- 一、MyBatis简介
- 1.1 框架概念
- 1.2 常用框架
- 1.3 MyBatis介绍
- 二、MyBatis框架部署
- 2.1 创建Maven项目
- 2.2 在项目中添加MyBatis依赖
- 2.3 创建MyBatis配置文件(添加自定义模板的方法)
- 三、MyBatis框架使用
- 3.1 创建数据表
- 3.2 创建实体类
- 3.3 创建DAO接口,定义操作方法
- 3.4 创建DAO接口的映射文件
- 3.5 将映射文件添加到主配置文件
- 四、单元测试
- 4.1 添加单元测试依赖
- 4.2 创建单元测试类
- 五、MyBatis的CRUD操作
- 5.1 添加操作
- 5.2 删除操作
- 5.3 修改操作
- 5.4 查询操作—查询所有
- 5.5 查询操作—查询一条记录
- 5.6 查询操作—多参数查询
- 5.7 查询操作—查询总记录数
- 5.8 添加操作回填生成的主键
- 六、MyBatis工具类封装
- 七、事务管理
- 7.1 手动提交事务
- 7.2 自动提交事务
1.2 常用框架框架,就是软件的半成品,完成了软件开发过程中的通用操作,程序员只需很少或者不用进行加工就能够实现特定的功能,从而简化开发人员在软件开发中的步骤,提高开发效率。
- MVC框架:简化了Servlet的开发步骤
- Struts2
- SpringMVC
- 持久层框架:完成数据库操作的框架
- apache DBUtils
- Hibernate
- Spring JPA
- MyBatis
- 胶水框架:Spring
SSM框架:Spring SpringMVC MyBatis
SSH框架: Spring Struts2 Hibernate
1.3 MyBatis介绍MyBatis是一个半自动的ORM框架
半自动
iBatis
Hibernate 全自动。但是由于它的高度封装,导致我有新的需求而它无法满足时,弊端就暴露出来了。
MyBatis 它是允许你添加一些自定义配置的。半自动带来的灵活,显出了它的优势。
ORM
ORM(Object Relational Mapping)对象关系映射。将Java中的一个对象与数据表中一行记录一一对应。
ORM框架提供了实体类与数据表的映射关系,通过映射文件的配置,实现对象的持久化。
- MyBatis特点:
- 支持自定义SQL、存储过程
- 对原有的JDBC进行了封装,几乎消除了所有JDBC代码,让开发者只需关注SQL本身
- 支持XML和注解配置方式自动完成ORM操作,实现结果映射
2.1 创建Maven项目框架部署,就是将框架引入到我们的项目中
- Java工程
- Web工程
-
在pom.xml中添加依赖
-
mybatis
org.mybatis mybatis 3.4.6 -
mysql driver
mysql mysql-connector-java 8.0.27 (这些依赖可以在“maven repository的官网上查询到”)
我把依赖添加好了,是不是就可以开始使用MyBatis了?
不。你还要配置数据源,因为你的MyBatis还不知道你的数据库在哪呢,你要告诉它数据库在哪,怎么连数据库(账号、密码是什么)。
-
创建自定义模板(以mybatis-config.xml文件为例)
首先,找好自己要定义的模板内容。
然后,在File->Settings->Editor中找到File and Code Templates,点“+”号。
最后,在Name:里填写文件名,Extension:填写文件后缀,再将模板内容贴到下面,并将下面两个单选框全都勾中表示激活。之后点击确定即可。
可以看到,以后我们在New文件的时候,都会有这个mybatis-config.xml模板了:
- 在resources中创建名为mybatis-config.xml的文件
- 在mybatis-config.xml文件中配置
- 今天只配一个mysql:
三、MyBatis框架使用
3.1 创建数据表案例:学生信息的数据库操作
tb_students
CREATE TABLE tb_students( sid int PRIMARY key auto_increment, stu_num char(5) not null unique, stu_name varchar(20) not null, stu_gender char(2) not null, stu_age int not null );3.2 创建实体类
Student.java
package com.wyl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
private int stuId;
private String stuNum;
private String stuName;
private String stuGender;
private int stuAge;
}
详细过程如下:
- 创建两个包:dao和pojo。
- 在pojo中创建一个Student实体类。要注意:这个类中,属性的个数和类型,要和数据表保持一致(没有说属性名要一模一样)。
package com.wyl.pojo;
public class Student {
private int stuId;
private String stuNum;
private String stuName;
private String stuGender;
private int stuAge;
}
- 实体类中还要插入get、set方法,此时可以引入lombok。
org.projectlombok lombok 1.18.12 provided
- 使用lombok提供的方式
package com.wyl.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data //get、set方法
@AllArgsConstructor //全参构造器
@NoArgsConstructor //无参构造器
@ToString //ToString方法
public class Student {
private int stuId;
private String stuNum;
private String stuName;
private String stuGender;
private int stuAge;
}
3.3 创建DAO接口,定义操作方法
StudentDAO.java
package com.wyl.dao;
import com.wyl.pojo.Student;
public interface StudentDAO {
public int insertStudent(Student student);
public int deleteStudent(String stuNum);
}
3.4 创建DAO接口的映射文件
- 在resources目录下,新建名为mappers的文件夹(实际上这个名字可以随便取,只是习惯这样取)。
- 在mappers中新建名为StudentMapper.xml的映射文件(可以自定义模板)。
- 在映射文件中对DAO中定义的方法进行实现:
StudentMapper.xml
3.5 将映射文件添加到主配置文件insert into tb_students(stu_num,stu_name,stu_gender,stu_age) values(#{stuNum},#{stuName},#{stuGender},#{stuAge}) delete from tb_students where stu_num=#{stuNum}
mybatis-config.xml
四、单元测试 4.1 添加单元测试依赖...
4.2 创建单元测试类junit junit 4.12
在被测试类名后Alt+Insert —— 选择Test
之后,在test文件夹下会生成一个对应的Test类
对测试类写具体的方法:
package com.wyl.dao;
import com.wyl.pojo.Student;
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;
import static org.junit.Assert.*;
public class StudentDAOTest {
@org.junit.Test
public void insertStudent() {
try {
//加载mybatis配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//会话工厂(我build的内容是配置文件,所以要先获取Resources)
SqlSessionFactory factory = builder.build(is);
//会话(连接)
SqlSession sqlSession = factory.openSession();
//通过会话获取DAO对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
//测试StudentDAO中的方法
int i = studentDAO.insertStudent(new Student(0, "10001", "张三", "男", 21));
//需要手动提交
sqlSession.commit();
System.out.println(i);
} catch (IOException e) {
e.printStackTrace();
}
}
@org.junit.Test
public void deleteStudent() {
}
}
调哪个类的方法,就使用哪个类的对象。
五、MyBatis的CRUD操作5.1 添加操作案例:学生信息的增删改查
略
5.2 删除操作根据学号删除一条学生信息
- 在StudentDAO中定义删除方法
public interface StudentDAO {
public int insertStudent(Student student);
public int deleteStudent(String stuNum);
}
- 在StudentMapper.xml中对接口方法进行“实现”
...(略) delete from tb_students where stu_num=#{stuNum}
- 测试:在StudentDAO的测试类中添加测试方法
@Test
public void testDeleteStudent(){
}
测试方法一般是无返回值的void类型。
方法名前缀加一个test。
方法前加一个@Test注解,使它可以单独运行。
调哪个类的方法,就使用哪个类的对象。
所以要获取一个StudentDAO类的对象。
但是由于StudentDAO是一个接口,我们获取它的对象肯定不能直接new一个对象出来。
- MyBatis给我们提供了一个SQLSession类,它里面提供了获取对象的方法。
@Test
public void testDeleteStudent(){
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = null;
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
sqlSession.getMapper() 可以得到所有DAO的对象,所以在括号里我们要加上StudentDAO.class。
-
那么怎么获得SqlSession呢?
通过工厂方法设计模式。
@Test
public void testDeleteStudent(){
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = null;
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
只要有了SqlSessionFactory,我就能得到我的SqlSession。
-
那么问题是,SqlSessionFactory又是怎么得到的?
通过SqlSessionFactoryBuilder。而它是可以直接new的。
通过 .build() 方法就可以了。但是它是要和数据库进行连接的。所以要在括号里写数据库配置文件,通过流的形式将数据库配置文件中的配置信息读出来。
@Test
public void testDeleteStudent(){
//SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = builder.build();
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
-
builder.build()括号里要传入数据库配置文件的流信息。
所以我们要通过Resources来获取。
@Test
public void testDeleteStudent(){
Resources.getResourceAsStream("mybatis-config.xml");
//SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = builder.build();
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
注意 .getResourceAsStream(" ")中的文件名不要写错。也正是因为文件名可能写错,于是这条语句要做一个异常处理,try、catch一下。
@Test
public void testDeleteStudent(){
try {
Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = builder.build();
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
-
最后再把外面的代码段都移动到try内。
因为try的代码段中,有一个语句抛出异常了,那么它后面的语句都不执行了。
如果不把外面的代码移到try里面,那么就算抛出异常,外面的代码依旧会执行。
@Test
public void testDeleteStudent(){
try {
Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = builder.build();
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
} catch (IOException e) {
e.printStackTrace();
}
}
-
这是一个输入流还是输出流?是一个输入流。
所以InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
再把 is 给 builder.build(is);
@Test
public void testDeleteStudent(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//SqlSessionFactory表示MyBatis的会话工厂
SqlSessionFactory factory = builder.build(is);
//SqlSession表示MyBatis与数据库之间的会话(所谓的会话就是一次连接)
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
} catch (IOException e) {
e.printStackTrace();
}
}
-
从上往下理解逻辑:
- 整个配置文件mybatis-config.xml,被加载成一个流。通过is读取到配置信息。
- 同时,由于我的StudentMapper.xml这个mapper,是放在了mybatis-config.xml文件中的:
- 然后把is给到了 .build() 方法,得到了factory。于是factory里面有了数据库连接信息。
- 有了数据库连接信息,就能创建会话。于是我的SqlSession可以建立连接,连接到数据库。
- 而且我不光有数据库连接的信息,我还有mapper文件。有了mapper文件,我就可以读取文件中的SQL。
- 我根据你标签的名字,有、等。将它们动态的封装成一个虚拟类,给到StudentDAO。
- 然后返回一个StudentDAO对象。这个StudentDAO对象,实际上就是封装了、的这个虚拟类的对象,它并不是接口的真实对象。但不管怎么说,我的StudentDAO现在包含了两个方法(一个insert的,一个delete的),那么我studentDAO现在就可以调用这两个方法。这两个方法就可以执行。[实际上是后面讲的动态代理]
- 图示:(红色代表加载过程;蓝色是动态封装的过程,蓝色都是由MyBatis做的)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IRADZ6vd-1639316781271)(C:Users11202AppDataRoamingTyporatypora-user-images1639301779069.png)]
-
最后调用studentDAO.deleteStudent()方法即可。注意要sqlSession.commit()手动提交一下。
//调用被测试方法
int i = studentDAO.deleteStudent("10001");
//提交事务
sqlSession.commit();
5.3 修改操作
根据学生学号,修改其他字段信息
-
第一步:在StudentDAO中定义修改方法。
-
在StudentMapper.xml文件中配置StudentDAO接口的方法实现,带有标签的SQL语句,注意标签id要与DAO中的方法名一致。
[注:update的SQL语句,其中set+字段名中的字段名可以直接去数据库表上复制过来,防止输入错误,同时调整一下格式以增加可读性]
update tb_student set stu_name=#{stuName}, stu_gender=#{stuGender}, stu_age=#{StuAge} where stu_num=#{stuNum}
- 第三步:在StudentDAOTest测试类中添加测试方法。
@Test
public void testUpdateStudent(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
int i = studentDAO.updateStudent(new Student(0, "10001", "赵六", "女", 18));
sqlSession.commit();
assertEquals(0,i);
} catch (IOException e) {
e.printStackTrace();
}
}
注意:sout(i)然后返回1时可以表示测试方法成功执行。但是测试方法中,可以使用断言来更好的完成这一效果。
例如返回值是int,可以使用assertEquals(1,i);。它表示我的 i 期望的返回值为1。
如果达不到期望值会怎样呢,例如将期望值改为0,而正确执行方法的返回值为1。如图:
5.4 查询操作—查询所有这叫断言异常。
- 第一步:在StudentDAO接口中定义查询方法。
public interface StudentDAO {
public int insertStudent(Student student);
public int deleteStudent(String stuNum);
public int updateStudent(Student student);
public List listStudents();
}
注意,查询所有学生记录,返回的是Student类型,而且不止是一个,所以返回值是List。
-
在StudentMapper.xml文件中配置StudentDAO接口的方法实现,带有标签的SQL语句。要注意id要跟对应的方法名称一致。
注意,select语句尽量不要用select *。
//为什么要加resultType="com.wyl.pojo.Student",见“第三步:测试”
>>注意:我就这样去写测试类的话,运行肯定会报错。因为我select查询数据表的时候,它数据表中的一个记录,我是要装到一个实体类对象中去的。但是,因为SQL语句中的sid,stu_num…与我实体类中的属性名stuId,stuNum…是不一样的,而且我也没有建立起对应关系,所以我不知道该如何对应匹配来存放。所以我要在SQL语句中起别名:
>>注意:除了“起别名”这种解决方案外,还有另一种解决方法。通过标签,定义映射关系。它的id是可以随便取的(此处叫做"studentMap"),type要给到你的实体类。主键用表示,其他列用表示。带有两个属性:column和property,column与数据表的字段名一致,property跟类的属性名一致。定义好“规则”之后,我的标签想去运用这个规则,于是要对其标签加上resultMap="studentMap"
>>建议使用第二种方案。其一,如果有多个查询,我可以直接引用一下我的studentMap就行了,便于复用。其二,现在只是简单映射,如果做复杂查询、关联查询的时候,它还可以做复杂的映射关系。这个映射标签后面会单独再讲。
- 第三步:测试。
@Test
public void testListStudents(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
List list = studentDAO.listStudents();
for (Student stu:list){
System.out.println(stu);
}
} catch (IOException e) {
e.printStackTrace();
}
}
发现报错,Map的问题。
A query was run and no Result Maps were found for the Mapped Statement 'com.wyl.dao.StudentDAO.listStudents'. It's likely that neither a Result Type nor a Result Map was specified.
因为虽然我们给SQL语句中属性起了别名,但是并没有告诉它返回值是什么,而且返回值又是我们自定义的类型,不是系统默认的一些类型。所以要去StudentMapper.xml的标签处写上返回值类型。也就是你要把查询的结果,放到什么对象内。不然我为什么会把查询操作执行的结果就放到Student类而不是其他的例如Book类中呢?
这时,再去执行测试类,可以正确得到结果。
5.5 查询操作—查询一条记录根据学号查询一个学生的信息
- 第一步:在StudentDAO接口中定义查询方法。
public interface StudentDAO {
public int insertStudent(Student student);
public int deleteStudent(String stuNum);
public int updateStudent(Student student);
public List listStudents();
public Student queryStudent(String stuNum);
}
- 第二步:在StudentMapper.xml文件中配置StudentDAO接口的方法实现,带有标签的SQL语句(相比于全部查询多了一个where)。
- 第三步:单元测试。
@Test
public void testQueryStudent(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
Student student = studentDAO.queryStudent("10001");
System.out.println(student);
} catch (IOException e) {
e.printStackTrace();
}
}
5.6 查询操作—多参数查询
分页查询(参数 start,pageSize)
-
第一步:在StudentDAO接口中定义查询方法。
!!在方法命名上的一个问题:
虽然java的类从语法角度,当然支持方法的重载。
public ListlistStudents(); public List listStudents(int start, int pageSize);
但是它在 .xml文件中进行配置时,标签的id就一样了。虽然我们可以通过添加一些标签参数进行解决,但是为了满足xml规范,标签的id是唯一的这个特性,所以建议在接口中还是不要用方法的重载。(不是说语法不支持,是因为这样会破坏我们公认的xml规范)
所以,正确的定义如下。
public interface StudentDAO {
public int insertStudent(Student student);
public int deleteStudent(String stuNum);
public int updateStudent(Student student);
public List listStudents();
public Student queryStudent(String stuNum);
public List listStudentsByPage(int start, int pageSize);
}
- 第二步:在StudentMapper.xml文件中配置StudentDAO接口的方法实现,带有标签的SQL语句。
- 第三步:测试。
@Test
public void testListStudentsByPage(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
//从第0条开始,每页展示2条记录
List list = studentDAO.listStudentsByPage(0,2);
for (Student stu:list){
System.out.println(stu);
}
} catch (IOException e) {
e.printStackTrace();
}
}
- 发现报错,没有找到参数。
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘start’ not found. Available parameters are [arg1, arg0, param1, param2]
Cause: org.apache.ibatis.binding.BindingException: Parameter ‘start’ not found. Available parameters are [arg1, arg0, param1, param2]
为什么,我在StudentDAO中明明定义了
public List
你这里给的参数,SQL无法取到。但是,为什么呢?刚才public Student queryStudent(String stuNum);这个按学号查询信息的方法,明明它就能取到它括号里的参数。
做个测试,我把“按学号查询”这个方法当中,mapper.xml中的SQL语句改为
依然能够正确的执行我的测试类。
>>这说明:对于MyBatis进行操作(不一定是查询操作,也可以是添加操作或其他操作)
1.如果操作方法只有一个简单类型或者字符串类型的参数,在Mapper配置中可以直接通过#{str}直接获取(此处的str是任意的,你起成你的属性名也行,取个aaa也行);
- 比如public Student queryStudent(String stuNum);
2.如果操作方法中有一个对象类型的参数,在Mapper配置中可以直接通过#{attrName}获取对象的指定属性值(attrName必须是参数对象的属性);
- 比如public int insertStudent(Student student);
insert into tb_students(stu_num,stu_name,stu_gender,stu_age) values(#{stuNum},#{stuName},#{stuGender},#{stuAge}) //这里的#{}必须严格遵守
3.如果操作方法有一个Map类型的参数,在Mapper配置中可以直接通过#{key}获取key对应的value。
比如public List
listStudentsByPage( ? );
如果我这样写public List
listStudentsByPage(int start, int pageSize); 在我调用listStudentsByPage(0,2);的时候会报错说无法获取参数start。
我们可以通过HashMap,传入一个参数map,map中包含这两个属性就可以了:
public List
listStudentsByPage(HashMap map); 调用的时候,就不能传个(0,2)了,而是要定义一个HashMap的对象:
HashMapmap = new HashMap (); map.put("start",0); map.put("pageSize",2); List list = studentDAO.listStudentsByPage(map); 执行,可以正确进行分页查询。
- 那么它既然传递的是一个map,那么我在mapper.xml传值的时候,只需传map中的key就可以了。
4.如果操作方法有多个参数,该如何处理呢?
5.7 查询操作—查询总记录数
我不想用Map,觉得Map麻烦,我就想用
public List
listStudentsByPage(int start,int pageSize);,该怎么处理? 再看报错内容:
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘start’ not found. Available parameters are [arg1, arg0, param1, param2]
Cause: org.apache.ibatis.binding.BindingException: Parameter ‘start’ not found. Available parameters are [arg1, arg0, param1, param2]
什么意思呢,也就是说,你的mapper配置文件中的SQL语句,要像这样写:
运行成功。
但是这两种写法(arg、param),都需要我们去记住参数的顺序,比较麻烦。
于是这里可以有一个小操作,当我的操作方法有多个参数的时候,我给每个参数取个别名,通过MyBatis提供的@Param(“xxx”)
public ListlistStudentsByPage(@Param("start") int start, @Param("pageSize") int pageSize); 然后在Mapper文件中的SQL语句里就可以写:
现在,这个#{start}中的“start”,指的就不是参数名称int start了,而是@Param(“start”)中给参数起的那个别名。
这种方法用的很常见。
- 第一步:StudentDAO
public int getCount();
- 第二步:mapper文件添加配置
- 第三步:测试
@Test
public void testGetCount(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
int count = studentDAO.getCount();
System.out.println(count);
} catch (IOException e) {
e.printStackTrace();
}
}
- 运行,会报错
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘com.wyl.dao.StudentDAO.getCount’. It’s likely that neither a Result Type nor a Result Map was specified.
因为,执行SQL语句的返回值,要么是resultMap,要么是resultType,总之是一个对象。但是你这里select count,它返回的不是对象,只是一个int。
所以要用resultType指定当前操作的返回值类型为int。
再次运行,成功。
5.8 添加操作回填生成的主键- 在我们执行插入语句后,想要获取记录的id,但是由于id是自增的,所以它只有在记录生成后才能被获取到。
即使你说插入操作完成后再执行一次查询操作,不光麻烦,而且你也不知道哪条记录是你刚刚添加的啊。
//测试StudentDAO中的方法
Student student = new Student(0, "10004", "Lucy", "女", 21);
int i = studentDAO.insertStudent(student);
System.out.println(student);
输出结果:
Student(stuId=0, stuNum=10004, stuName=Lucy, stuGender=女, stuAge=21)
- 但是在很多时候我们都希望获取到这个id的值,比如订单管理等功能。我们要用到主键回填。
在mapper文件的对应方法标签中,设置如下两个属性:
insert into tb_students(stu_num,stu_name,stu_gender,stu_age)
values(#{stuNum},#{stuName},#{stuGender},#{stuAge})
- 这个时候我们再去运行
//测试StudentDAO中的方法
Student student = new Student(0, "10005", "Lily", "女", 21);
int i = studentDAO.insertStudent(student);
System.out.println(student);
输出结果:
Student(stuId=8, stuNum=10005, stuName=Lily, stuGender=女, stuAge=21)
可以看到stuId是自增后得到的8,而不是SQL语句给的0了。
六、MyBatis工具类封装-
我们在很多的操作方法中,有很多代码段都是重复的、一模一样的。
例如,在每一个方法的测试类,都包含有
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
我们可以把它封装成一个工具类。
public class MyBatisUtil {
public static SqlSession getSqlSession() {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
return factory.openSession();
}
}
但是有一个问题,我们这个factory实际上是单例的。也就是,factory只需要创建一次就可以了。而不需要每次都生成一个factory。
只用做一次的东西,到static静态代码块中去做。
public class MyBatisUtil {
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return factory.openSession();
}
}
但是这样写,由于作用域的问题,我获取不到static里面定义的factory,于是再修改一下:
public class MyBatisUtil {
private static SqlSessionFactory factory;
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return factory.openSession();
}
}
- 简单的实现,到这里就实现出来了。但是我们还要考虑一个问题,我的getSqlSession()有没有可能被并发地访问?要给它弄一把“锁”。线程锁。
private static final ThreadLocallocal = new ThreadLocal ();
有了它,每次调用的时候会生成一个副本,线程之间不会相互影响。
下面的方法,由于每次返回一个SqlSession对象时都factory.openSession(),每次都open,会造成资源浪费,于是改为:
public static SqlSession getSqlSession() {
SqlSession sqlSession = local.get();
return sqlSession;
}
但是这样也不行,第一次调的时候local里面还是null。
public static SqlSession getSqlSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null){
sqlSession = factory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
- 到此,整个工具类就写完了。
public class MyBatisUtil {
private static SqlSessionFactory factory;
private static final ThreadLocal local = new ThreadLocal();
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession sqlSession = local.get();
if (sqlSession == null){
sqlSession = factory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
}
- 因此,我的测试类的代码可以进行调整(以插入的测试类为例):
@Test
public void testInsertStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//通过会话获取DAO对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
//测试StudentDAO中的方法
Student student = new Student(0, "10005", "Lily", "女", 21);
int i = studentDAO.insertStudent(student);
System.out.println(student);
//需要手动提交
sqlSession.commit();
System.out.println(i);
}
-
现在,我只需要一句话,就能得到SqlSession了。但是每次得到SqlSession后,都要sqlsession.getMapper()。既然你们所有人都要getMapper,那么我可以在我的工具类里面写,给你getMapper()一下。
public static
T getMapper(Class C){ } 用泛型来定义方法。也就是,你传入的参数是StudentDAO类型,我的方法返回值就是StudentDAO类型了。
public static
T getMapper(Class c){ SqlSession sqlSession = getSqlSession(); return sqlSession.getMapper(c); } -
这样一来。我的一些操作,例如增删改,是需要事务管理的,也就是手动提交,sqlSession.commit();而查询操作是不需要事务管理的。这些不需要事务管理的操作根本就不需要SqlSession,而是只需要获取DAO即可。(实际上,需不需要提交,就是看数据有没有发生改变了)
@Test
public void testListStudents() {
StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class);
List list = studentDAO.listStudents();
for (Student stu : list) {
System.out.println(stu);
}
}
- 那么我的帮助类,现在就有两种用法:
- 如果你需要事务管理,那么你就先得到SqlSession,再通过sqlSession.getMapper(),最后再进行提交操作。
- 如果你不需要事务管理,你直接调用我自定义的MyBatisUtil中的getMapper。SqlSession就不需要了。
7.1 手动提交事务SqlSession对象
- getMapper(DAO.class):获取Mapper(DAO接口的实例)
- 事务管理
- sqlSession.commit();提交事务
- sqlSession.rollback();事务回滚
@Test
public void testInsertStudent() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
// 1.当我们获取SqlSession对象时,就默认开启了事务
try {
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
Student student = new Student(0, "10005", "Lily", "女", 21);
int i = studentDAO.insertStudent(student);
System.out.println(student);
//2.操作完成并成功之后,需要手动提交
sqlSession.commit();
} catch (Exception e) {
//3.当操作出现异常,调用rollback方法进行回滚
sqlSession.rollback();
}
}
这样,成功操作,就会提交;操作失败,就会进行回滚。
7.2 自动提交事务通过SqlSessionFactory调用openSession对象时,可以通过参数设置事务是否自动提交:
如果参数设置为true,表示自动提交事务:factory.openSession(true);
如果参数设置为false,或者不设置参数,表示手动提交:
factory.openSession(false); / factory.openSession();
@Test
public void testDeleteStudent() {
SqlSessionFactory factory = null;
//factory.openSession(boolean isAutoCommit); 是否自动提交事务,默认值为false
SqlSession sqlSession = factory.openSession();
//通过SqlSession对象调用getMapper方法获取DAO接口对象
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
}
如果SqlSession sqlSession = factory.openSession(true);
那么最后不用写sqlSession.commit(); 也会自动提交。
- 于是我们可以将我们的帮助类进行一个优化:
public static SqlSession getSqlSession(boolean isAutoCommit) {
SqlSession sqlSession = local.get();
if (sqlSession == null){
sqlSession = factory.openSession(isAutoCommit);
local.set(sqlSession);
}
return sqlSession;
}
-
但是设为true,会有一个问题。我们在实际场景中,往往是多个操作连续进行,例如:添加商品insert操作后,要跟着进行修改库存的update操作。而这两个操作要么同时成功,要么同时失败,不能是插入失败,但库存更新了。所以,如果设置为true,那么会在insert执行成功后,我直接就提交了,哪怕第二步update在执行过程中执行失败,我的insert也提交了。所以,它就不能保证,这两个或多个操作属于同一个事务。
-
所以,要通过SqlSession进行多个步骤的操作的时候,建议这里写成false,当我所有的步骤完成之后,再手动提交。
-
比如一个事务有三个步骤:操作1、操作2、操作3。我手动提交,在三个操作后面写sqlSession.commit();
这样一来,三个操作有一个出现异常,最后都提交不了。
rollback的功能是什么。比如操作1、操作2成功了,但是操作3出现异常,那么会对操作1、操作2进行一个回滚。
所以可以达到这三个操作的一致性。
但是如果你只有一个操作,就直接可以设置为自动事务提交。
-
那么我帮助类中的getMapper()方法内,在调用SqlSession的时候,是设置为true还是false呢?
设置为true。因为你getMapper(),就是只想获得DAO,就是不想,也不需要进行手动事务管理。
public static
T getMapper(Class c){ SqlSession sqlSession = getSqlSession(true); return sqlSession.getMapper(c); } -
如果觉得这样还是不够简化,可以再次封装:
private static SqlSession getSqlSession(boolean isAutoCommit) {
SqlSession sqlSession = local.get();
if (sqlSession == null){
sqlSession = factory.openSession(isAutoCommit);
local.set(sqlSession);
}
return sqlSession;
}
//手动事务管理
public static SqlSession getSqlSession(){
return getSqlSession(false);
}
//自动事务提交
public static T getMapper(Class c){
SqlSession sqlSession = getSqlSession(true);
return sqlSession.getMapper(c);
}
只对外提供两个方法。想进行手动事务管理,就调用第一个方法;想进行自动提交,就调用第二个方法。
- MyBatisUtil.java最终版本:
public class MyBatisUtil {
private static SqlSessionFactory factory;
private static final ThreadLocal local = new ThreadLocal();
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
private static SqlSession getSqlSession(boolean isAutoCommit) {
SqlSession sqlSession = local.get();
if (sqlSession == null){
sqlSession = factory.openSession(isAutoCommit);
local.set(sqlSession);
}
return sqlSession;
}
public static SqlSession getSqlSession(){
return getSqlSession(false);
}
public static T getMapper(Class c){
SqlSession sqlSession = getSqlSession(true);
return sqlSession.getMapper(c);
}
}



