JavaSSM框架从入门到精通!第二天(MyBatis(一))!
一、 Mybatis 框架
1. Mybatis 框架简介
Mybatis 是 apache 的一个开源项目,名叫 iBatis ,2010 年这个项目由 apache 迁移到了 google,并命名为 Mybatis,2013 年迁移到了 GitHub,可以在 GitHub 下载源码。
2. Mybatis 的下载:https://github.com/mybatis
3. Mybatis 解压后的目录
使用的时候需要导入核心 jar 包,依赖的 jar 包以及数据库的驱动即可使用。
二、Mybatis 概述
1. Mybatis
Mybatis 是一个基于 Java 语言的持久层框架,它内部封装了 JDBC,使开发人员只需要关注 SQL 语句本身,其他的如注册驱动,创建 Connection,配置 Statement等繁杂操作,Mybatis 已经完成。Mybatis 通过 XML 配置或注解的方式将要执行的各种 Statement 配置起来,通过 Java 对象和 Statement 中的 SQL 动态参数(类似于占位符?)进行映射形成可执行的 SQL 语句,由 Mybatis 执行后将返回结果封装成 Java 对象返回。
2. Mybatis 的原理图:
3. 开发第一个 Mybatis 应用
(1) 导入 jar 包
导入 mybatis 的核心 jar 包,依赖 jar 包以及 mysql 的驱动 jar 包
在 IDEA 中新建一个 Java 项目,然后在工程上右键,新建一个 lib 目录,将上述的所有 jar 包拷贝到 lib 目录中,然后右键 lib 目录,Add As Library 添加为工程的库:
(2) 创建数据库表
(3) 创建 JavaBean
注意:Mybatis 要求,JavaBean 的属性名需要和数据库表的字段名同名(当然,也可以不同名,不同名可以使用后面讲的 resultMap 来解决)
package com.edu.beans;
public class Student {
private long id;
private String name;
private int age;
private double score;
public Student() {
}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public Student(long id, String name, int age, double score) {
this.id = id;
this.name = name;
this.age = age;
this.score = score;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
(4) 创建 Dao 层接口
package com.edu.dao;
import com.edu.beans.Student;
public interface IStudentDao {
void insertStudent(Student student);
}
(5) 定义映射文件 mapper.xml,放在 com.edu.dao 包下面
映射文件的定义可以参考 Mybatis 的帮助文档,搜索“mapper.dtd”:
(6) 定义主配置文件 mybatis.xml
主配置文件的定义可以参考 Mybatis 的帮助文档,搜索 config.dtd
在定义主配置文件之前,我们先定义连接数据库的四要素的属性文件:jdbc.properties,要放在类路径中,即 src 目录:
然后定义 Mybatis 的主配置文件,也是放在类路径下,即 src 目录下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--
mybatis-3-config.dtd 是 Mybatis 主配置文件约束文件,在 mybatis 核心jar包 mybatis-3.3.0.jar的这里:
org.apache.ibatis.builder.xml.mybatis-3-config.dtd
-->
<configuration>
<!--注册连接数据库四要素的属性文件:jdbc.properties-->
<properties resource="jdbc.properties"/>
<!--
<environments> 用于定义环境,其中可以包含多个 <environment> 的定义,它的 default 属性用于指明当前使用哪一个环境
-->
<environments default="development">
<!--<environment> 就是其中一个环境的定义,id 属性就是该环境的名称-->
<environment id="development">
<!--指定事务管理器,type="JDBC" 表示使用 JDBC 自身的事务管理机制-->
<transactionManager type="JDBC"/>
<!-- 数据源的配置,type="POOLED" 表示使用 Mybatis 内置的数据库连接池-->
<dataSource type="POOLED">
<!--
从 jdbc.properties 属性文件中读取连接数据库的四要素来给 Mybatis 的数据源的属性赋值,
这里使用 EL 表达式的形式读取属性文件
-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--指定映射文件的位置:要包含映射文件的完整路径-->
<mapper resource="com/edu/dao/mapper.xml"/>
</mappers>
</configuration>
(7) 定义 Dao 层实现类
package com.edu.dao.impl;
import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class StudentDaoImpl implements IStudentDao {
//Mybatis 的会话对象
private SqlSession session;
@Override
public void insertStudent(Student student) {
try {
//读取主配置文件 mybatis.xml,得到输入流
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建 SqlSessionFactory 工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//由工厂对象产生 SqlSession 对象
session = factory.openSession();
//通过 SqlSession 对象调用 API 来执行映射文件mapper.xml中的 SQL 语句
/**
* SqlSession 的 insert() 方法会去读取 mapper.xml 中配置的 <insert> 语句,该方法有两个参数:
* 第一个参数:映射文件 mapper.xml 中 <insert> 标签的 id 名
* 第二个参数:传递给 mapper.xml 的 SQL 语句动态参数的对象,该对象的属性值会给同名的动态参数赋值
*/
session.insert("insertStudent", student);
//提交事务
session.commit();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭会话 session
if (session != null){
session.close();
}
}
}
}
(8) 测试类
package com.edu.test;
import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;
public class StudentTest {
public static void main(String[] args) {
IStudentDao studentDao = new StudentDaoImpl();
studentDao.insertStudent(new Student("晓晓",21,98.5));
}
}
4. 使用工具类
(1) 工具类
由于每次执行都需要 SqlSession 对象,而 SqlSession 对象获取比较麻烦,所以我们可以写一个工具类来获取该对象,SqlSession 是由 SqlSesstionFactory 对象创建的,通过查SqlSessionFactory 的源码,发现其是一个重量级(开销大)的对象,所以可以将 SqlSessionFactory 设计成单例的,创建对象后不去关闭它,而是等到应用退出的时候自动释放,创建工具类如下:
package com.edu.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory factory;
public static SqlSession getSqlSession(){
try {
if(factory == null){//懒汉式单例模式
//读取主配置文件 mybatis.xml,得到输入流
InputStream in = Resources.getResourceAsStream("mybatis.xml");
//创建 SqlSessionFactory 工厂对象
factory = new SqlSessionFactoryBuilder().build(in);
}
} catch (IOException e) {
e.printStackTrace();
}
return factory.openSession();
}
}
(2) 修改 Dao 层实现类
package com.edu.dao.impl;
import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.utils.MybatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class StudentDaoImpl implements IStudentDao {
//Mybatis 的会话对象
private SqlSession session;
@Override
public void insertStudent(Student student) {
try {
session = MybatisUtils.getSqlSession();
//通过 SqlSession 对象调用 API 来执行映射文件mapper.xml中的 SQL 语句
/**
* SqlSession 的 insert() 方法会去读取 mapper.xml 中配置的 <insert> 语句,该方法有两个参数:
* 第一个参数:映射文件 mapper.xml 中 <insert> 标签的 id 名
* 第二个参数:传递给 mapper.xml 的 SQL 语句动态参数的对象,该对象的属性值会给同名的动态参数赋值
*/
session.insert("insertStudent", student);
//提交事务
session.commit();
} finally {
//关闭会话 session
if (session != null){
session.close();
}
}
}
}
(3) 测试类
package com.edu.test;
import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;
public class StudentTest {
public static void main(String[] args) {
IStudentDao studentDao = new StudentDaoImpl();
studentDao.insertStudent(new Student("小明",23,98.5));
}
}
5. 多个映射文件
(1) 在一个项目中,一个 Dao 接口对应一个映射文件,所以一个项目中有多个映射文件时很普遍的
(2) 项目示例:
1) 新加一个映射文件:将原来的复制一份
运行:
此时运行程序报错,原因时两个 mapper 的 namespace 相同,并且 <insert> 的 id 也相同,冲突了。
2) 修改任意一个映射文件
将两个映射文件的 SQL 语句,归于不同的命名空间(namesapce),命名空间的作用用于区分 SQL 映射的同名的 id 值,例如:
此时又报错了,原因时两个映射文件都有一个 SQL 映射的 id 为 “insertStudent”,Mybatis 就不知道该用哪一个 mapper 的 “insertStudent”,因为我们在 Java 代码中并没有指定:
解决办法就是在 Java 代码的 insert() 方法的第一个参数给出包含 namespace 前缀的 SQL 语句的 id 值:
再次执行:
没有问题。
6. 主配置文件详解(mybatis.xml)
(1) 注册连接数据库的四要素属性文件(jdbc.properties)
jdbc.properties 属性文件要放在类路径下,即 src 目录下,注意,如果项目包含 resources 目录,那么 resources 目录就是工程的类路径,此时 jdbc.properties 文件就要放在 resources 目录下。
(2) 主配置文件:mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--
mybatis-3-config.dtd 是 Mybatis 主配置文件约束文件,在 mybatis 核心jar包 mybatis-3.3.0.jar的这里:
org.apache.ibatis.builder.xml.mybatis-3-config.dtd
-->
<configuration>
<!--注册连接数据库四要素的属性文件:jdbc.properties-->
<properties resource="jdbc.properties"/>
<!--
<environments> 用于定义环境,其中可以包含多个 <environment> 的定义,它的 default 属性用于指明当前使用哪一个环境
-->
<environments default="development">
<!--<environment> 就是其中一个环境的定义,id 属性就是该环境的名称-->
<environment id="development">
<!--
指定事务管理器,type="JDBC" 表示使用 JDBC 自身的事务管理机制,即使用 Connection 对象的 commit() 方法
提交事务,rollback() 方法回退事务
-->
<transactionManager type="JDBC"/>
<!-- 数据源的配置,type="POOLED" 表示使用 Mybatis 内置的数据库连接池-->
<dataSource type="POOLED">
<!--
从 jdbc.properties 属性文件中读取连接数据库的四要素来给 Mybatis 的数据源的属性赋值,
这里使用 EL 表达式的形式读取属性文件
-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--指定映射文件的位置:要包含映射文件的完整路径-->
<mapper resource="com/edu/dao/mapper.xml"/>
<mapper resource="com/edu/dao/mapper2.xml"/>
</mappers>
</configuration>
(3) 指定实体类的全类名的别名
我们在映射文件中经常会使用到实体类,如果不给出别名,那么每次都要写全类名,比较麻烦,我们可以通过如下的方式给实体类的全类名取别名,例如:
1) 没有取别名的情况:
2) 取别名方式一:<package>
使用 <package> 方式,这样的好处是可以将指定包下面的所有实体类的简单类名作为别名,例如:
mybatis.xml 主配置文件:
mapper.xml 映射文件就可以使用别名了:
3) 方式二: <typeAlias>
使用 <typeAlias> 标签指定,它有两个属性,type:全类名,alias:别名,这种方式可以取任意的别名,但是需要一个一个指定,例如:
mybatis.xml:
mapper.xml:
(4) <enviroments>标签
该标签内可以包含多个 <enviroment> 标签,即可以配置多个运行环境,其 default 属性用于表示当前使用哪一个环境,例如:
(5) <transactionManager>标签:事务管理器
其 type 属性有两个取值:
JDBC:表示使用 Connection 对象的 commit() 和 rollback() 来提交或回滚事务。 MANAGED:表示使用容器来管理事务,比如 Spring 容器。
(6) <dataSource>标签:数据源,即数据库连接池
其 type 属性有三个取值:
UNPOOLED:不使用数据库连接池,每次请求,都会创建连接对象,使用完毕关闭连接。 POOLED:使用 Mybatis 自带的数据库连接池。 JNDI:数据源可以定义到应用的外部,通过 JNDI 容器获取数据库连接。
(7) 指定映射文件的位置,有多种方式:
1) 通过 <mapper resource="">指定,例如:
<mapper resource="com/edu/dao/mapper.xml"/>
<mapper resource="com/edu/dao/mapper2.xml"/>
2) 通过 <mapper url="">指定,例如:
<mapper url="file:///F:/workspace/mapper.xml"/>
<mapper url="http://localhost:8080/chapter2/mapper.xml"/>
该方式的好处是可以将映射文件放在本地或网络中的任意位置,通过 URL 地址访问。
3) 通过 <mapper class="">指定,例如:
<mapper class="com.edu.dao.IStudentDao"/>
使用该方式需要满足的条件:
1. 映射文件名要和 Dao 接口的名称相同
2. 映射文件要和 Dao 接口放在同一个包中
3. 映射文件中的<mapper>标签的 namespace 属性值要为 Dao 接口的全类名,如下:
4) <package name="">指定映射文件
当映射文件比较多的时候,可以使用该方式,其 name 属性用于指定映射文件所在的包,它会将这个包下面的所有映射文件都导入。但是使用这种方式,需要用到我们后面讲的 mapper 动态代理,必须满足如下条件:
1. Dao 层要使用 mapper 动态代理
2. 映射文件名要和 Dao 接口的名称相同
3. 映射文件要和 Dao 接口放在同一个包中
4. 映射文件中的<mapper>标签的 namespace 属性值要为 Dao 接口的全类名
7. API 讲解
(1) Resources 类
用于读取资源文件,返回 IO 流对象,例如:
InputStream in = Resources.getResourceAsStream("mybatis.xml");
(2) SqlSessionFactoryBuilder 类
调用其 build() 方法可以创建 SqlSessionFactory 对象,SqlSessionFactoryBuilder 对象创建完工厂对象后,即可被销毁,所以一般将其定义为局部变量,方法结束,对象自动销毁,例如:
factory = new SqlSessionFactoryBuilder().build(in);
(3) SqlSessionFactory 接口
是一个重量级对象,所以一个应用只需一个该对象即可,可以使用单例模式来创建,它包含获取 SqlSession 的方法有:
1. openSession(true):创建自动提交事务功能的 SqlSession。
2. openSession(false):创建手动提交事务功能的 SqlSession。
3. openSession():创建手动提交事务功能的 SqlSession。
(4) SqlSession 接口
一个 SqlSession 对象对应一次 Mybatis 会话,会话以该对象创建开始,以该对象关闭结束,SqlSession 不是线程安全的,所以每次会话结束前,需要将其 close(),再次会话,再次创建,常用方法:
package org.apache.ibatis.session;
import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.executor.BatchResult;
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1);
<E> List<E> selectList(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
<K, V> Map<K, V> selectMap(String var1, String var2);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
(5) 源码分析
1) 输入流的关闭
我们发现,代码中获取输入流 in 之后并没有去关闭,我们分析一下,查看这个代码:
1factory = new SqlSessionFactoryBuilder().build(in);
跟进 build() 方法:
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
继续跟进 build() 方法:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();//代码里面自动帮我们关闭了输入流
} catch (IOException var13) {
}
}
return var5;
}
可以看到在 finally 语句块中,始终都会去关闭输入流。
2) SqlSession 的创建
SqlSession 是通过 SqlSessionFactory 的 openSession() 方法创建的,而 SqlSessionFactory 接口的实现类是 DefaultSqlSessionFactory 类,我们查看该类的 openSession() 方法:
继续跟进去:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();//从主配置文件读取环境信息
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);//通过环境信息创建事务工厂
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//通过事务工厂创建事务对象
Executor executor = this.configuration.newExecutor(tx, execType);//创建执行 SQL 语句的执行体对象
//通过上面的对象来创建 DefaultSqlSession 对象,即 SqlSession 对象
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;//返回 SqlSession 对象
}
继续跟到 DefaultSqlSession 内部:
在创建 SqlSession 对象的时候,进行了一些对象的初始化,其中 dirty=false 表示现在数据库的数据还没有被修改,如果修改了数据,dirty 为 true。
3) 增删改的执行
对于 SqlSession 的 insert()、delete()、update() 方法,底层都是调用了 update() 方法,在 DefaultSqlSession 源码中可以看到:
public int insert(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int delete(String statement, Object parameter) {
return this.update(statement, parameter);
}
public int update(String statement) {
return this.update(statement, (Object)null);
}
继续跟到 update() 方法中:
public int update(String statement, Object parameter) {
int var4;
try {
this.dirty = true;//由于现在要修改数据了,所以将 dirty=true
//从映射文件中获取配置好的 statement
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过执行体执行已经填充了动态参数的 SQL 语句
var4 = this.executor.update(ms, this.wrapCollection(parameter));
} catch (Exception var8) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + var8, var8);
} finally {
ErrorContext.instance().reset();
}
return var4;
}
4) SqlSession 的 commit() 方法,去 DefaultSqlSession 实现类中查看:
public void commit() {
this.commit(false);
}
继续跟进去:
public void commit(boolean force) {
try {
this.executor.commit(this.isCommitOrRollbackRequired(force));//通过执行体来提交事务
this.dirty = false;//事务提交后,数据修改已经完成,将 dirty = false
} catch (Exception var6) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + var6, var6);
} finally {
ErrorContext.instance().reset();
}
}
继续跟进 isCommitOrRollbackRequired(false):
private boolean isCommitOrRollbackRequired(boolean force) {
//this.autoCommit 为 false,this.dirty 为 true,force 为 false,整个结果为 true
return !this.autoCommit && this.dirty || force;
}
回到上面的 commit() 方法:
继续跟进 executor 的 commit() 方法,executor 的实现类是 BaseExecutor:
public void commit(boolean required) throws SQLException {
if (this.closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
} else {
this.clearLocalCache();
this.flushStatements();
if (required) {//由于上面传过来的 required 为 true,所以下面的事务会提交
this.transaction.commit();
}
}
}
5) SqlSession 的 close() 方法,去 DefaultSqlSession 中查看
public void close() {
try {
//this.isCommitOrRollbackRequired(false):上面计算过,返回 true
this.executor.close(this.isCommitOrRollbackRequired(false));
this.dirty = false;//关闭 session 时,dirty 也要置为 false
} finally {
ErrorContext.instance().reset();
}
}
继续跟进 executor 的 close() 方法,实现类是 BaseExecutor:
public void close(boolean forceRollback) {
try {
try {
this.rollback(forceRollback);//关闭 session 之前始终会去回滚事务
} finally {
if (this.transaction != null) {
this.transaction.close();
}
}
} catch (SQLException var11) {
log.warn("Unexpected exception on closing transaction. Cause: " + var11);
} finally {
this.transaction = null;
this.deferredLoads = null;
this.localCache = null;
this.localOutputParameterCache = null;
this.closed = true;
}
}
通过上面分析, SqlSession 在关闭之前会先去回滚事务,然后再关闭资源,所以我们不需要在代码中写 rollback() 的代码了。
如果我们更新数据的时候成功并已经提交了事务,那么关闭 session 时就会去回滚,由于事务已经提交,回滚就没有用了,但是如果更新数据时,发生了异常,即事务没有提交,那么关闭 session 时就自动将事务回滚了。
三、单表的增删改查(CURD)
1. 开发步骤
(1) 修改 Dao 接口
(2) insertStudentCatchId(Student student):插入后用新插入的 id 初始化被插入的对象
1) 修改映射文件 mapper.xml
2) 修改 Dao 层实现类 StuentDaoImpl.java
3) 测试类
使用 Junit 单元测试,需要导入 Junit 的 jar 包:hamcrest-core-1.3.jar、junit-4.12.jar,同时我们也可以使用 log4j 来打印日志信息,需要将 log4j 的配置文件(log4j.properties)放在类路径下,即 src 目录:
编写 Junit 的单元测试:
package com.edu.test;
import com.edu.beans.Student;
import com.edu.dao.IStudentDao;
import com.edu.dao.impl.StudentDaoImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
//JUnit 的测试类
public class StudentTest {
private IStudentDao studentDao;
@Before //被 @Before 注解的方法为初始化方法,它会在每次 @Test 修饰的方法执行之前执行一次,完成初始化工作
public void setUp(){
studentDao = new StudentDaoImpl();
}
@After //被 @After 注解的方法为销毁方法,它会在 @Test 修饰的方法执行之后执行一次,完成资源的销毁工作
public void tearDown(){
}
@Test //被 @Test 注解的方法为测试方法,可以有多个这样的方法
public void testInsertStudent(){
studentDao.insertStudent(new Student("张三",21,98.5));
}
@Test
public void testInsertStudentCatchId(){
Student student = new Student("王五",21,98.5);
System.out.println("插入数据前,student=" + student);
studentDao.insertStudentCatchId(student);
System.out.println("插入数据后,student=" + student);
}
}
(3) 删除数据:deleteStudentById
1) 修改 mapper.xml 映射文件
2) 修改 StudentDaoImpl.java
3) 测试类 StudentTest
(4) 修改数据 updateStudent
1) 修改 mapper.xml
2) 修改 StudentDaoImpl.java
3) 测试类 StudentTest
(5) 查询所有对象,返回 List:selectAllStudents
1) 修改 mapper.xml:
2) 主配置文件配置别名:
3) 修改 StudentDaoImpl.java
4) 测试类 StudentTest
(6) 查询所有对象,返回 Map:selectStudentMap
1) 修改 mapper.xml
2) 修改 StudentDaoImpl.java
要实现 selectStudentMap() 方法,需要使用 session 的 selectMap() 方法完成,该方法会将查询出来的每条记录先封装成指定对象,然后再将该对象作为 Map 的 value,将该对象的指定属性对应的字段名作为 key 封装成一个 Map 对象,方法原型:
Map<K, V> selectMap(String id, String mapKey); id:映射文件中配置的 SQL 语句的 id mapKey:查询出的 Map 使用的key,这个可以为数据库表的字段名
3) 测试类 StudentTest
(7) 查询单个对象:selectStudentById
1) 修改 mapper.xml:
<select id="selectStudentById" resultType="Student">
select * from student where id=#{xxx}
</select>
2) 修改 StudentDaoImpl.java
2) 测试类
(8) map 作为查询条件:selectStudentsByMap
1) 修改 mapper.xml
<select id="selectStudentsByMap" resultType="Student">
select * from student where name=#{map.name} and age = #{map.age}
</select>
2) 修改 StudentDaoImpl.java
3) 测试类 StudentTest
(9) 模糊查询:selectStudentsByName
1) 修改 mapper.xml,模糊查询有两种写法:
2) 修改 StudentDaoImpl.java
3) 测试类 StudentTest
注意:Mybatis中的 "$" 和 "#" 区别很大:其中"#"表示占位符,而"$"符号为字符串拼接("#"不存在注入式攻击,"$"可能存在注入式攻击)
示例:将上面示例的 mapper.xml 中的"#{}"改为"${}":
执行测试代码,查看日志:
可以看到,“$” 符号是字符串拼接形式,是硬编码的方式,可能存在注入式攻击危险。
然后我们又将其改为 “#{}” 动态参数方式:
<select id="selectStudentsByName" resultType="Student">
<!--注意:'%' 和 #{xxx} 之间有一个空格-->
select * from student where name like '%' #{xxx} '%'
</select>
执行测试代码,查看日志:
可以看到,“#” 是占位符的方式给参数赋值,不存在注入式攻击危险。
(10) JavaBean 属性名和表字段名不同名
映射文件中的 resultType 可以将查询结果直接封装成 JavaBean 对象返回的条件是:SQL 语句中的字段名要和JavaBean的属性同名,如果不同名,就无法创建出对应的对象返回,这里有两种解决方案:
1) 第一种:给表字段取别名,让别名和 JavaBean 属性同名
修改 JavaBean:
package com.edu.beans;
public class Student {
private long id;
private String name;
private int age;
private double tscore;
public Student() {
}
public Student(String name, int age, double tscore) {
this.name = name;
this.age = age;
this.tscore = tscore;
}
public Student(long id, String name, int age, double tscore) {
this.id = id;
this.name = name;
this.age = age;
this.tscore = tscore;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getTscore() {
return tscore;
}
public void setTscore(double tscore) {
this.tscore = tscore;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", tscore=" + tscore +
'}';
}
}
修改 mapper.xml,给表字段取别名,让别名和 JavaBean 的属性同名:
<select id="selectStudentById" resultType="Student">
select id,name,age,score tscore from student where id=#{xxx}
</select>
测试类:StudentTest:
2) 第二种:使用结果映射 resultMap
可以使用结果映射 resultMap 来建立映射,完成表字段到属性的映射,达到将查询结果封装为对象的目的,resultMap 是对 resultType 的增强。
修改 mapper.xml:
测试类 StudentTest: