栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【学习笔记】MyBatis部署使用|CRUD|单元测试|事务管理|工具类封装|IDEA添加自定义模板

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【学习笔记】MyBatis部署使用|CRUD|单元测试|事务管理|工具类封装|IDEA添加自定义模板

文章目录
  • 一、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 自动提交事务

一、MyBatis简介 1.1 框架概念

框架,就是软件的半成品,完成了软件开发过程中的通用操作,程序员只需很少或者不用进行加工就能够实现特定的功能,从而简化开发人员在软件开发中的步骤,提高开发效率。

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操作,实现结果映射
二、MyBatis框架部署

框架部署,就是将框架引入到我们的项目中

2.1 创建Maven项目
  • Java工程
  • Web工程
2.2 在项目中添加MyBatis依赖
  • 在pom.xml中添加依赖

    • mybatis

      
          org.mybatis
          mybatis
          3.4.6
      
      
    • mysql driver

      
          mysql
          mysql-connector-java
          8.0.27
      
      

      (这些依赖可以在“maven repository的官网上查询到”)

    我把依赖添加好了,是不是就可以开始使用MyBatis了?

    不。你还要配置数据源,因为你的MyBatis还不知道你的数据库在哪呢,你要告诉它数据库在哪,怎么连数据库(账号、密码是什么)。

2.3 创建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





    
    
    
    
    
    
    
        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}
    

3.5 将映射文件添加到主配置文件

mybatis-config.xml




    
		...
    

    
        
    

四、单元测试 4.1 添加单元测试依赖

            junit
            junit
            4.12

4.2 创建单元测试类

在被测试类名后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 sid stuId,stu_num stuNum,stu_name stuName,stu_gender stuGender,stu_age stuAge
   from tb_students

​ >>注意:除了“起别名”这种解决方案外,还有另一种解决方法。通过标签,定义映射关系。它的id是可以随便取的(此处叫做"studentMap"),type要给到你的实体类。主键用表示,其他列用表示。带有两个属性:column和property,column与数据表的字段名一致,property跟类的属性名一致。定义好“规则”之后,我的标签想去运用这个规则,于是要对其标签加上resultMap="studentMap"

    
	
        
        
        
        
        
    

		
    
    select sid stuId,stu_num stuNum,stu_name stuName,stu_gender stuGender,stu_age stuAge
    from tb_students

这时,再去执行测试类,可以正确得到结果。

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)。
    
        select sid ,stu_num ,stu_name ,stu_gender ,stu_age
        from tb_students
        limit #{start},#{pageSize}
    
  • 第三步:测试。
    @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 listStudentsByPage(int start, int pageSize);的啊。

你这里给的参数,SQL无法取到。但是,为什么呢?刚才public Student queryStudent(String stuNum);这个按学号查询信息的方法,明明它就能取到它括号里的参数。

做个测试,我把“按学号查询”这个方法当中,mapper.xml中的SQL语句改为

    
        select sid ,stu_num ,stu_name ,stu_gender ,stu_age
        from tb_students
        where stu_num=#{aaa}	//我这里取#{stuNum}也行,#{aaa}也行
    

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的对象:

                HashMap map = new HashMap();
                map.put("start",0);
                map.put("pageSize",2);
                List list = studentDAO.listStudentsByPage(map);
    

    ​ 执行,可以正确进行分页查询。

    • 那么它既然传递的是一个map,那么我在mapper.xml传值的时候,只需传map中的key就可以了。

4.如果操作方法有多个参数,该如何处理呢?

  • 我不想用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语句,要像这样写:

    
        select sid ,stu_num ,stu_name ,stu_gender ,stu_age
        from tb_students
        limit #{start},#{pageSize}
    

现在,这个#{start}中的“start”,指的就不是参数名称int start了,而是@Param(“start”)中给参数起的那个别名。

这种方法用的很常见。

5.7 查询操作—查询总记录数
  • 第一步:StudentDAO
public int getCount();
  • 第二步:mapper文件添加配置
    
        select count(1) from tb_students
    

所以要用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 ThreadLocal local = 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就不需要了。
七、事务管理

SqlSession对象

  • getMapper(DAO.class):获取Mapper(DAO接口的实例)
  • 事务管理
7.1 手动提交事务
  • 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);
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/657957.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号