一、MyBatis简介

1.1 框架概念

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

1.2 常用框架

  • MVC框架:简化了Servlet的开发步骤
    • Struts
    • Struts2
    • SpringMVC
  • 持久层框架:完成数据库操作的框架
    • apache DBUtils
    • Hibernate
    • Spring JPA
    • MyBatis
    • EJB3.0
  • 胶水框架:Spring

SSM Spring SpringMVC MyBatis

SSH Spring Struts2 Hibernate

1.3 MyBatis介绍

MyBatis是一个半自动ORM框架

ORM(Object Relational Mapping)对象关系映射,将Java中的一个对象与数据表中一行记录一一对应。

ORM框架提供了实体类与数据表的映射关系,通过映射文件的配置,实现对象的持久化。

  • MyBatis的前身是iBatis,iBatis是Apache软件基金会提供的一个开源项目
  • 2010年iBatis迁移到Google code,正式更名为MyBatis
  • 2013年迁移到Github托管
  • MyBatis特点:
    • 支持自定义SQL、存储过程
    • 对原有的JDBC进行了封装,几乎消除了所有JDBC代码,让开发者只需关注SQL本身
    • 支持XML和注解配置方式自定完成ORM操作,实现结果映射

二、MyBatis框架部署

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

2.1 创建Maven项目

  • Java工程
  • Web工程

2.2 在项目中添加MyBatis依赖

  • 在pom.xml中添加依赖
    • mybatis
    • mysql driver ```xml mysql mysql-connector-java 5.1.47
org.mybatis mybatis 3.4.6 <a name="9cad03d2"></a> #### 2.3 创建MyBatis配置文件 - 创建自定义模板:选择resources----右键New----Edit File Templates<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055521376-b01a6dd2-55a8-4fc0-848e-0c355a52fac1.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=479&id=u00d89236&margin=%5Bobject%20Object%5D&name=image.png&originHeight=695&originWidth=907&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89664&status=done&style=none&taskId=uddc7a948-10a9-4df9-9885-4e6598d34ee&title=&width=624.9784853203562) - 在resources中创建名为`mybatis-config.xml`的文件 - 在`mybatis-config.xml`文件配置数据库连接信息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"> <a name="dfbcf85e"></a> ## 三、MyBatis框架使用 > 案例:学生信息的数据库操作 <a name="07ad1e1a"></a> #### 3.1 创建数据表 | tb_students | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055825344-ea559693-32ec-4e50-a5c3-2b68101c9742.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=136&id=ue49dbfa3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=198&originWidth=954&originalType=binary&ratio=1&rotation=0&showTitle=false&size=20050&status=done&style=none&taskId=u443b440e-d4be-4a77-b621-03313978f88&title=&width=657.3643605243878) |sql 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 ); <a name="d5bfeebf"></a> #### 3.2 创建实体类 | Student.java | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055838681-1e77900a-cb57-4a51-8a18-fa063b447a52.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u4dbf7fce&margin=%5Bobject%20Object%5D&name=image.png&originHeight=370&originWidth=957&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24390&status=done&style=none&taskId=u03ee8615-96cb-41fe-b082-9cbb9a376e4&title=&width=659.4315440480494) |java package com.chang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; //使用lombok包进行便捷操作 @Data @AllArgsConstructor @NoArgsConstructor @ToString public class Student { private int stuId; private String stuNum; private String stuName; private String stuGender; private int stuAge; } <a name="7dac4300"></a> #### 3.3 创建DAO接口,定义操作方法 | StudentDAO.java | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055850166-e10259d8-394b-4bac-8064-49e6e7c7a333.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=101&id=ua1642235&margin=%5Bobject%20Object%5D&name=image.png&originHeight=147&originWidth=1008&originalType=binary&ratio=1&rotation=0&showTitle=false&size=11129&status=done&style=none&taskId=ucf87af0f-5358-4e07-9925-8381028762d&title=&width=694.5736639502966) |java package com.chang.dao; import com.chang.pojo.Student; public interface StudentDao { public int insertStudent(Student student); public int deleteStudent(String stuNum); } <a name="b8a60544"></a> #### 3.4 创建DAO接口的映射文件 - 在`resources`目录下,新建名为`mappers`文件夹 - 在`mappers`中新建名为`StudentMapper.xml`的映射文件(根据模板创建) - 在映射文件中对DAO中定义的方法进行实现:xml <?xml version=”1.0” encoding=”UTF-8” ?> <!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 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} <a name="4f28ec4a"></a> #### 3.5 将映射文件添加到主配置文件 | mybatis-config.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055883720-6421b0c8-94c7-4873-8a93-80d5d6e46ae5.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=298&id=ue7d7b6c6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=433&originWidth=1169&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52451&status=done&style=none&taskId=u6044dcdc-1009-4dc9-9c0e-94f03ccf860&title=&width=805.512513053469) | <a name="86b2c2fb"></a> ## 四、单元测试 <a name="281997c8"></a> #### 4.1 添加单元测依赖xml junit junit 4.12 <a name="d6ad4e3a"></a> #### 4.2 创建单元测试类 | 在被测试类名后alt+insert --- 选择Test | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055894339-b015742d-17a7-4c6e-8d41-7e6e7ce005aa.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=101&id=u1a1ee70a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=147&originWidth=859&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12417&status=done&style=none&taskId=ue95cafd4-1973-4b6a-bfd9-f0cacbb9239&title=&width=591.9035489417706) | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055898597-886a015b-f525-495e-a378-bb01d00e0149.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=274&id=u12fb3379&margin=%5Bobject%20Object%5D&name=image.png&originHeight=398&originWidth=553&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29257&status=done&style=none&taskId=u778a6e5d-7de0-4983-ac0d-a650a54a93d&title=&width=381.0508295282877) | <a name="a6de24d4"></a> #### 4.3 测试代码java package com.qfedu.dao; import com.qfedu.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(); //MyBatis的会话工厂 SqlSessionFactory factory = builder.build(is); //会话(连接) 表示MyBatis与数据库之间的会话,通过工厂方法设计模式 SqlSession sqlSession = factory.openSession(); //通过会话调用getMapper()方法,获取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() { } } <a name="6d7be89e"></a> ## 五、MyBatis的CRUD操作 > 案例:学生信息的增删查改 <a name="25cf42a7"></a> #### 5.1 添加操作 同测试方法。 <a name="034db1e8"></a> #### 5.2 删除操作 > 根据学号删除一条学生信息 - 在StudentDAO中定义删除方法 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055911969-4626fda8-2466-4a87-87bb-42a3a36d2890.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=98&id=u954541b5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=142&originWidth=1035&originalType=binary&ratio=1&rotation=0&showTitle=false&size=10303&status=done&style=none&taskId=ua854e0f1-ec2c-4387-b04a-5a1bdabbbaf&title=&width=713.1783156632509) | - 在StudentMapper.xml中对接口方法进行“实现” | StudentMapper.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055921096-8cbb6896-c3b9-4692-965d-34c2a2b28eba.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=283&id=u1aff2e1b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=411&originWidth=1186&originalType=binary&ratio=1&rotation=0&showTitle=false&size=42974&status=done&style=none&taskId=u6ede185e-827a-4de9-868a-44bbd920d2e&title=&width=817.2265530208847) | - 测试:在StudentDAO的测试类中添加测试方法 | StudentDAOTest | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055932562-71c5ff90-8a28-45bd-bcb8-c425c846ac7f.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=542&id=u9373fec1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=787&originWidth=1130&originalType=binary&ratio=1&rotation=0&showTitle=false&size=75470&status=done&style=none&taskId=u402a09bd-3751-45f0-80e8-4cca2bdb8e7&title=&width=778.6391272458682) | <a name="0ff33aaf"></a> #### 5.3 修改操作 > 根据学生学号,修改其他字段信息 - 在StudentDAO接口中定义修改方法 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055943002-ee14015f-8325-4024-a03e-bb892738a4ad.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=120&id=u7153c770&margin=%5Bobject%20Object%5D&name=image.png&originHeight=174&originWidth=1123&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12769&status=done&style=none&taskId=u6f1b1ea6-b4fc-4901-aff5-98f2576b54c&title=&width=773.8156990239911) | - 在StudentMapper.xml中“实现”接口中定义的修改方法 | StudentMapper.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055950363-ca4ab046-c915-4268-9aeb-00ec27f7c429.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=420&id=u54461763&margin=%5Bobject%20Object%5D&name=image.png&originHeight=609&originWidth=1198&originalType=binary&ratio=1&rotation=0&showTitle=false&size=57046&status=done&style=none&taskId=u2c7bf3a4-c053-404c-9eef-d1a4e6fa9e7&title=&width=825.495287115531) | - 单元测试 | StudentDAOTest | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055976772-54e46548-2978-456e-a1c3-7d99aa029808.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=420&id=u5dbb1c5a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=610&originWidth=1421&originalType=binary&ratio=1&rotation=0&showTitle=false&size=57329&status=done&style=none&taskId=u70911d97-beea-46c8-b064-fa1d0533fcd&title=&width=979.1559290410431) | <a name="89c888b2"></a> #### 5.4 查询操作-查询所有 - 在StudentDAO接口定义操作方法 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055993318-6b7c875d-6164-4a19-9a0b-05a76a551f22.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=158&id=ue54b2333&margin=%5Bobject%20Object%5D&name=image.png&originHeight=230&originWidth=1134&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15474&status=done&style=none&taskId=u6e9e0cf6-5b6f-475c-adc7-0b2c65aa87f&title=&width=781.3953719440836) | - 在StudentMapper.xml中“实现”DAO中定义的方法 | StudentMapper.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644055998426-93787ab7-f1d0-4660-9b78-7b35bb432219.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=443&id=ua3506a2f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=643&originWidth=1277&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89586&status=done&style=none&taskId=u89b25c32-4713-470d-afd9-a0f5a764350&title=&width=879.9311199052864) | - 单元测试 | StudentDAOTest | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056004578-328da47c-29ce-4fd3-9a3f-e76b8f743b9c.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=377&id=u5aac7bc5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=547&originWidth=1270&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39673&status=done&style=none&taskId=u13ff1884-3ad6-4bfe-9d96-33437aa5ea0&title=&width=875.1076916834094) | <a name="ab8d6116"></a> #### 5.5 查询操作-查询一条记录 > 根据学号查询一个学生信息 - 在StudentDAO接口中定义方法 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056013005-93575f5d-6a75-4d46-9ac0-e24c39bd9d99.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=175&id=uf573e9fa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=254&originWidth=1089&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18439&status=done&style=none&taskId=uf3485d67-c274-464a-8454-60a43a0aeab&title=&width=750.3876190891597) | - 在StudentDAOMapper.xml中配置StudentDAO接口的方法实现——SQL | StudentDAOMapper.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056020077-590cb4f2-5d14-4f7d-a6db-0100b07cb925.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=101&id=udee5daca&margin=%5Bobject%20Object%5D&name=image.png&originHeight=147&originWidth=1165&originalType=binary&ratio=1&rotation=0&showTitle=false&size=14264&status=done&style=none&taskId=uf0f78ed5-3bd7-4431-84ce-9a9d3fb66de&title=&width=802.7562683552535) | - 单元测试 | StudentDAOTest | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056024598-f366d7af-bd57-40c0-baeb-e3876ddf4fe6.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=297&id=u2f83d35d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=431&originWidth=1172&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35600&status=done&style=none&taskId=uaf332c50-ee4a-48be-a3c5-21d313ed47c&title=&width=807.5796965771306) | <a name="b4d85e12"></a> #### 5.6 查询操作-多参数查询 > 分页查询(参数 start , pageSize) - 在StudentDAO中定义操作方法,如果方法有多个参数,使用`@Param`注解声明参数的别名 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056031879-f148567b-f619-48d2-b731-e38d590d58b3.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=362&id=ud05634c4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=525&originWidth=1156&originalType=binary&ratio=1&rotation=0&showTitle=false&size=82953&status=done&style=none&taskId=ud8cbcb1c-eddd-49e8-bb12-4aaa2eae9b5&title=&width=796.5547177842686) | - 在StudentMapper.xml配置sql时,使用`#{别名}`获取到指定的参数 | StudentMapper.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056037625-ccc950fb-ca27-49c8-aadf-c58cc060fd84.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=143&id=uc18a831a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=207&originWidth=1189&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21106&status=done&style=none&taskId=u5675b72e-d2fd-4075-881c-0032891d852&title=&width=819.2937365445463) | `**注意**` 如果DAO操作方法没有通过@Param指定参数别名,在SQL中也可以通过`arg0,arg1...`或者`param1,param2,...`获取参数 <a name="a11570c1"></a> #### 5.7 查询操作-查询总记录数 - 在StudentDAO接口中定义操作方法 | StudentDAO | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056044231-372a80cd-9118-40ed-a328-2331309e4b91.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=220&id=ua49a9607&margin=%5Bobject%20Object%5D&name=image.png&originHeight=320&originWidth=1080&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30310&status=done&style=none&taskId=u41b85a0a-e787-4e48-8804-c0091147b1b&title=&width=744.1860685181749) | - 在StudentMapper.xml配置sql,通过resultType指定当前操作的返回类型为int | | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056051345-5e6593f6-da03-43f1-8562-bcfa449e2daa.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=62&id=u28b9f22c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=90&originWidth=974&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8859&status=done&style=none&taskId=u318fe112-c8ce-49ec-a55a-264ab8ba785&title=&width=671.1455840154651) | <a name="275aa596"></a> #### 5.8 添加操作回填生成的主键 - StduentMapper.xml的添加操作标签——`insert` xml insert into tb_students(stu_num, stu_name, stu_gender, stu_age) values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge}) <a name="088d03b8"></a> ## 六、MyBatis工具类封装 - MyBatisUtiljava 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 SqlSessionFactory getFactory(){ return factory; } public static SqlSession getSqlSession(){ SqlSession sqlSession = local.get(); if(sqlSession == null ){ sqlSession = factory.openSession(); local.set(sqlSession); } return sqlSession; } public static T getMapper(Class c){ SqlSession sqlSession = getSqlSession(); return sqlSession.getMapper(c); } } <a name="0049d530"></a> ## 七、事务管理 > SqlSession 对象 > > - getMapper(DAO.class) : 获取Mapper(DAO接口的实例) > - 事务管理 > <a name="0448755c"></a> #### 7.1 手动提交事务 - `sqlSession.commit();`提交事务 - `sqlSession.rollback();`事务回滚 **测试类中进行事务管理**java @Test public void insertStudent() { SqlSession sqlSession = MyBatisUtil.getSqlSession(); //1.当我们获取sqlSession对象时,就默认开启了事务 try{ //通过会话获取DAO对象 StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class); //测试StudentDAO中的方法 Student student = new Student(0, “10005”, “Lily”, “女”, 21); int i = studentDAO.insertStudent(student); //2.操作完成并成功之后,需要手动提交 sqlSession.commit(); }catch (Exception e){ //3.当操作出现异常,调用rollback进行回滚 sqlSession.rollback(); } } **业务逻辑层手动事务管理**java public class StudentServiceImpl implements StudentService { public boolean addStudent(Student student) { boolean b = false; SqlSession sqlSession = MyBatisUtil.getSqlSession(); try{ StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class); int i = studentDAO.insertStudent(student); b = i>0; sqlSession.commit(); }catch (Exception e){ sqlSession.rollback(); } return b; } } <a name="78d9d2a2"></a> #### 7.2 自动提交事务 > 通过SqlSessionFactory调用openSession方法获取SqlSession对象时,可以通过参数设置事务是否自动提交: > > - 如果参数设置为true,表示自定提交事务: factory.openSession(true); > - 如果参数设置为false,或者不设置参数,表示手动提交:factory.openSession();/factory.openSession(false); > **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(); } } public static SqlSessionFactory getFactory(){ return factory; } 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); } } **测试操作**java @Test public void testDeleteStudent() { StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); int i = studentDAO.deleteStudent(“10001”); } **业务逻辑层自动事务管理**java public class StudentServiceImpl implements StudentService { private StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); public boolean addStudent(Student student) { int i = studentDAO.insertStudent(student); boolean b = i>0; return b; } } <a name="4fa20afa"></a> ## 八、MyBatis主配置文件 > mybatis-config.xml 是MyBatis框架的主配置文件,只要用于配置MyBatis数据源及属性信息 <a name="d8b0b251"></a> #### 8.1 properties标签 > 用于设置键值对,或者加载属性文件 - 在resources目录下创建`jdbc.properties`文件,配置键值对如下:properties mysql_driver=com.mysql.jdbc.Driver mysql_url=jdbc:mysql://localhost:3306/db_2010_fmwy?characterEncoding=utf-8 ##mysql_url=jdbc:mysql://localhost:3306/db_2010_fmwy?characterEncoding=utf-8&useSSL=false mysql_username=root mysql_password=admin123 - 在mybatis-config.xml中通过`properties`标签引用`jdbc.properties`文件;引入之后,在配置environment时可以直接使用jdbc.properties的key获取对应的value | mybatis-config.xml | | --- | | ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056069346-a73bf9b6-cfc6-42db-acb1-a4c2c985a1fe.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=550&id=uca89c0d6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=798&originWidth=1179&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117397&status=done&style=none&taskId=u01bdfabb-6406-4c88-aa37-894e6ca0c46&title=&width=812.4031247990076) | <a name="889ed8f8"></a> #### 8.2 settings标签xml <a name="42129609"></a> #### 8.3 typeAliases标签xml <a name="2e4c8d98"></a> #### 8.4 plugins标签xml <a name="27c3bcc6"></a> #### 8.5 environments标签xml <a name="7fb2624e"></a> #### 8.6 mappers标签 > 加载映射配置(映射文件、DAO注解)xml <a name="55d60007"></a> #### 8.7、汇总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"> <a name="60d963e0"></a> ## 九、映射文件 <a name="2a6b3606"></a> #### 9.1 MyBatis Mapper初始化 > XML文件解析:读取xml文件中的标签配置封装到Java对象中 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23162011/1644056084497-d6a905b8-0565-400f-8c4b-89c43dbe6ff9.png#clientId=u36beb7e6-e526-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=490&id=u5950d3f4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=711&originWidth=1560&originalType=binary&ratio=1&rotation=0&showTitle=false&size=54102&status=done&style=none&taskId=u00447f1a-976b-4272-bd96-e1201a85b92&title=&width=1074.9354323040304) <a name="ec69b74d"></a> #### 9.2 mapper根标签 > mapper文件相当于DAO接口的‘实现类’,namespace属性要指定`实现`DAO接口的全限定名 <a name="c467ec04"></a> #### 9.3 insert标签 > 声明添加操作(sql: insert ...) > > **常用属性** > > id属性,绑定对应DAO接口中的方法 > > parameterType属性,用以指定接口中对应方法的参数类型(可省略) > > useGeneratedKeys属性, 设置添加操作是否需要回填生成的主键 > > keyProperty属性,指定回填的id设置到参数对象中的哪个属性 > > timeout属性,设置此操作的超时时间,如果不设置则一直等待 **主键回填**xml insert into tb_students(stu_num, stu_name, stu_gender, stu_age) values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge}) ```xml <insert id="insertStudent" > <selectKey keyProperty="stuId" resultType="java.lang.Integer"> select last_insert_id() </selectKey> insert into tb_students(stu_num, stu_name, stu_gender, stu_age) values (#{stuNum}, #{stuName}, #{stuGender}, #{stuAge}) </insert> #### 9.4 delete标签 > 声明删除操作 #### 9.5 update标签 > 声明修改操作 #### 9.6 select标签 > 声明查询操作 >
> - id属性, 指定绑定方法的方法名 > - parameterType属性,设置参数类型 > - resultType属性,指定当前sql返回数据封装的对象类型(实体类) > - resultMap属性,指定从数据表到实体类的字段和属性的对应关系 > - useCache属性,指定此查询操作是否需要缓存 > - timeout属性,设置超时时间 > #### 9.7 resultMap标签 xml <!-- resultMap标签用于定义实体类与数据表的映射关系(ORM) --> <resultMap id="studentMap" type="Student"> <id column="sid" property="stuId"/> <result column="stu_num" property="stuNum"/> <result column="stu_name" property="stuName"/> <result column="stu_gender" property="stuGender"/> <result column="stu_age" property="stuAge"/> </resultMap> #### 9.8 cache标签 > 设置当前DAO进行数据库操作时的缓存属性设置 xml <cache type="" size="" readOnly="false"/> #### 9.9 sql和include > SQL片段 xml <sql id="wanglaoji">sid , stu_num , stu_name , stu_gender , stu_age</sql> <select id="listStudents" resultMap="studentMap"> select <include refid="wanglaoji"/> from tb_students </select> ## 十、分页插件 > 分页插件是一个独立于MyBatis框架之外的第三方插件; #### 10.1 添加分页插件的依赖 > PageHelper xml <!-- pagehelper分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency> #### 10.2 配置插件 > 在mybatis的主配置文件mybatis-config.xml中通过plugins标签进行配置 xml <!--plugins标签,用于配置MyBatis插件(分页插件)--> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins> #### 10.3 分页实例 > 对学生信息进行分页查询 java @Test public void testListStudentsByPage() { StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); //sqlSession //相当于设置了拦截器 PageHelper.startPage(2,4); List<Student> students = studentDAO.listStudents(); PageInfo<Student> pageInfo = new PageInfo<Student>(students); //pageInfo中就包含了数据及分页信息 // List<Student> list = pageInfo.getList(); // list.forEach(System.out::println); } 带条件分页 java @Test public void testListStudentsByPage() { StudentDAO studentDAO = MyBatisUtil.getMapper(StudentDAO.class); //sqlSession PageHelper.startPage(2,4); //List<Student> students = studentDAO.listStudents(); List<Student> list = studentDAO.listStudentsByGender("女"); PageInfo<Student> pageInfo = new PageInfo<Student>(list); //pageInfo中就包含了数据及分页信息 } ## 十一、关联映射 #### 11.1 实体关系 > 实体——数据实体,实体关系指的就是数据与数据之间的关系 >
> 例如:用户和角色、房屋和楼栋、订单和商品 实体关系分为以下四种: 一对一关联 实例:人和身份证、学生和学生证、用户基本信息和详情 数据表关系: - 主键关联(用户表主键 和详情主键相同时,表示是匹配的数据)
image.png - 唯一外键关联
image.png 一对多关联多对一关联 实例: - 一对多: 班级和学生、 类别和商品、楼栋和房屋 - 多对一:学生和班级、 商品和类别 数据表关系: - 在多的一端添加外键和一的一段进行关联 *多对多关联
实例:用户和角色、角色和权限、房屋和业主、学生和社团、订单和商品 数据表关系:建立第三张关系表添加两个外键分别与两张表主键进行关联 用户(user_id) 用户角色表(uid,rid) 角色(role_id) #### 11.2 创建项目,部署MyBatis框架 - 创建web项目(maven)
xml <!-- 添加web依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> - 部署MyBatis框架 - 添加依赖 ```xml org.mybatis mybatis 3.4.6 mysql mysql-connector-java 5.1.47 org.projectlombok lombok 1.18.18 provided

junit junit 4.12 test

  1. - 配置文件
  2. - 帮助类
  3. ```java
  4. public class MyBatisUtil {
  5. private static SqlSessionFactory factory;
  6. private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
  7. static{
  8. try {
  9. InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
  10. factory = new SqlSessionFactoryBuilder().build(is);
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. public static SqlSessionFactory getSqlSessionFactory(){
  16. return factory;
  17. }
  18. public static SqlSession getSqlSession(boolean isAutoCommit){
  19. SqlSession sqlSession = local.get();
  20. if(sqlSession == null){
  21. sqlSession = factory.openSession(isAutoCommit);
  22. local.set(sqlSession);
  23. }
  24. return sqlSession;
  25. }
  26. public static SqlSession getSqlSession(){
  27. return getSqlSession(false);
  28. }
  29. public static <T extends Object>T getMapper(Class<T> c){
  30. SqlSession sqlSession = getSqlSession(true);
  31. return sqlSession.getMapper(c);
  32. }
  33. }

11.3 一对一关联

实例:用户—-详情

11.3.1 创建数据表
  1. -- 用户信息表
  2. create table users(
  3. user_id int primary key auto_increment,
  4. user_name varchar(20) not null unique,
  5. user_pwd varchar(20) not null,
  6. user_realname varchar(20) not null,
  7. user_img varchar(100) not null
  8. );
  9. -- 用户详情表
  10. create table details(
  11. detail_id int primary key auto_increment,
  12. user_addr varchar(50) not null,
  13. user_tel char(11) not null,
  14. user_desc varchar(200),
  15. uid int not null unique
  16. -- constraint FK_USER foreign key(uid) references users(user_id)
  17. );

11.3.2 创建实体类
  • User

    1. @Data
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. @ToString
    5. public class User {
    6. private int userId;
    7. private String userName;
    8. private String userPwd;
    9. private String userRealname;
    10. private String userImg;
    11. }
  • Detail

    1. @Data
    2. @NoArgsConstructor
    3. @AllArgsConstructor
    4. @ToString
    5. public class Detail {
    6. private int detailId;
    7. private String userAddr;
    8. private String userTel;
    9. private String userDesc;
    10. private int userId;
    11. }

11.3.3 添加操作(事务)
测试代码
image.png

11.3.4 一对一关联查询

在查询用户的同时关联查询出与之对应的详情

实体

User Detail
image.png image.png

映射文件

连接查询
image.png
子查询
image.png
image.png

11.4 一对多关联

案例:班级(1)—学生(n)

11.4.1 创建数据表
  1. -- 创建班级信息表
  2. create table classes(
  3. cid int primary key auto_increment,
  4. cname varchar(30) not null unique,
  5. cdesc varchar(100)
  6. );
  7. -- 创建学生信息表
  8. create table students(
  9. sid char(5) primary key,
  10. sname varchar(20) not null,
  11. sage int not null,
  12. scid int not null
  13. );

11.4.2 创建实体类
Clazz Student
image.png image.png

11.4.3 关联查询

当查询一个班级的时候, 要关联查询出这个班级下的所有学生

连接查询

连接查询映射配置
image.png

子查询

子查询映射配置
image.png
image.png

11.5 多对一关联

实例:学生(n)—班级(1)

当查询一个学生的时候,关联查询这个学生所在的班级信息

11.5.1 创建实体类
Student Clazz
image.png image.png

11.5.2 关联查询

连接查询

连接查询映射配置
image.png

子查询

子查询映射配置
image.png
image.png

11.6 多对多关联

案例:学生(m)—课程(n)

11.6.1 创建数据表
  1. -- 学生信息表(如上)
  2. -- 课程信息表
  3. create table courses(
  4. course_id int primary key auto_increment,
  5. course_name varchar(50) not null
  6. );
  7. -- 选课信息表/成绩表(学号、课程号、成绩)
  8. create table grades(
  9. sid char(5) not null,
  10. cid int not null,
  11. score int not null
  12. );

11.6.2 关联查询

查询学生时,同时查询学生选择的课程

Student Course
image.png image.png

根据课程编号查询课程时,同时查询选择了这门课程的学生

Student Course
image.png image.png
连接查询映射配置
image.png
子查询映射配置
image.png
image.png

十二、动态SQL

交友网:珍爱网、百合网 筛选心仪对象 性别 年龄 城市 身高

电商:淘宝、京东 筛选商品 羽毛球拍 品牌 价格

  1. 方江鹏 性别 select * from members where gender='女'
  2. 罗彪 性别 年龄 18-23 select * from members where gender='女' and age >=18 and age <=23
  3. 张三 年龄 城市 select * from members where age >=18 and age <=23 and city=''

用户的筛选条件不同,我们完成筛选执行的SQL也不一样;我们可以通过穷举来一一的完成不同条件的筛选,但是这种实现思路过于繁琐和复杂,MyBatis就提供了动态SQL的配置方式来实现多条件查询。

12.1 什么是动态SQL?

根据查询条件动态完成SQL的拼接

12.2 动态SQL使用案例

案例:心仪对象搜索

12.2.1 创建数据表
  1. create table members(
  2. member_id int primary key auto_increment,
  3. member_nick varchar(20) not null unique,
  4. member_gender char(2) not null,
  5. member_age int not null,
  6. member_city varchar(30) not null
  7. );

12.2.2 创建实体类
  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @ToString
  5. public class Member {
  6. private int memberId;
  7. private String memberNick;
  8. private String memberGender;
  9. private int memberAge;
  10. private String memberCity;
  11. }

12.2.3 创建DAO接口

在DAO接口中定义一个多条件查询的方法

  1. public interface MemberDAO {
  2. //在多条件查询中,如果查询条件不确定,可以直接使用HashMap作为参数
  3. //优点:无需单独定义传递查询条件的类
  4. //缺点:当向Map中存放参数时,key必须与动态sql保持一致()
  5. //public List<Member> searchMember(HashMap<String,Object> params);
  6. // 也可以定义专门用于存放查询条件的实体类存放参数
  7. //优点:设置参数时无需关注属性名
  8. //缺点:需要单独定义一个类来封装参数
  9. public List<Member> searchMember(MemberSearchCondition params);
  10. }

12.3 if

  1. <resultMap id="memberMap" type="Member">
  2. <id column="member_id" property="memberId"/>
  3. <result column="member_nick" property="memberNick"/>
  4. <result column="member_gender" property="memberGender"/>
  5. <result column="member_age" property="memberAge"/>
  6. <result column="member_city" property="memberCity"/>
  7. </resultMap>
  8. <select id="searchMember" resultMap="memberMap">
  9. select member_id,member_nick,member_gender,member_age,member_city
  10. from members
  11. where 1=1
  12. <if test="gender != null"> <!--gender 就是参数对象的属性/参数Map的key-->
  13. and member_gender=#{gender}
  14. </if>
  15. <if test="minAge != null">
  16. and member_age &gt;= #{minAge} <!-- &gt; -->
  17. </if>
  18. <if test="maxAge != null">
  19. and member_age &lt;= #{maxAge} <!-- &lt; -->
  20. </if>
  21. <if test="city != null">
  22. and member_city = #{city}
  23. </if>
  24. </select>

测试

  1. @Test
  2. public void testSearchMember() {
  3. HashMap<String,Object> params = new HashMap<String, Object>();
  4. params.put("gender","女");
  5. params.put("minAge",18);
  6. //params.put("maxAge",23);
  7. params.put("city","武汉");
  8. //-----------------------------------------------------------------------
  9. MemberSearchCondition params2 = new MemberSearchCondition();
  10. params2.setGender("女");
  11. //params2.setMinAge(21);
  12. //params2.setMaxAge(30);
  13. //params2.setCity("武汉");
  14. //==========================================================================
  15. MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);
  16. List<Member> members = memberDAO.searchMember(params2);
  17. for (Member m: members) {
  18. System.out.println(m);
  19. }
  20. }

12.4 where

  1. <select id="searchMember" resultMap="memberMap">
  2. select member_id,member_nick,member_gender,member_age,member_city
  3. from members
  4. <where>
  5. <if test="gender != null"> <!--gender 就是参数对象的属性/参数Map的key-->
  6. and member_gender=#{gender}
  7. </if>
  8. <if test="minAge != null">
  9. and member_age &gt;= #{minAge} <!-- &gt; -->
  10. </if>
  11. <if test="maxAge != null">
  12. and member_age &lt;= #{maxAge} <!-- &lt; -->
  13. </if>
  14. <if test="city != null">
  15. and member_city = #{city}
  16. </if>
  17. </where>
  18. order by member_age
  19. </select>

12.5 trim

  1. <select id="searchMember" resultMap="memberMap">
  2. select member_id,member_nick,member_gender,member_age,member_city
  3. from members
  4. <trim prefix="where" prefixOverrides="and | or" suffix="order by member_age">
  5. <if test="gender != null"> <!--gender 就是参数对象的属性/参数Map的key-->
  6. and member_gender=#{gender}
  7. </if>
  8. <if test="minAge != null">
  9. and member_age &gt;= #{minAge} <!-- &gt; -->
  10. </if>
  11. <if test="maxAge != null">
  12. and member_age &lt;= #{maxAge} <!-- &lt; -->
  13. </if>
  14. <if test="city != null">
  15. and member_city = #{city}
  16. </if>
  17. </trim>
  18. </select>

12.6 foreach

  1. public interface MemberDAO {
  2. //查询指定城市的会员
  3. public List<Member> searchMemberByCity(List<String> cities);
  4. }
  1. <select id="searchMemberByCity" resultMap="memberMap">
  2. select member_id,member_nick,member_gender,member_age,member_city
  3. from members where member_city in
  4. <foreach collection="list" item="cityName" separator="," open="(" close=")">
  5. #{cityName}
  6. </foreach>
  7. </select>

测试

  1. @Test
  2. public void searchMemberByCity() {
  3. List<String> cities = new ArrayList<String>();
  4. cities.add("厦门");
  5. cities.add("宜昌");
  6. MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);
  7. List<Member> members = memberDAO.searchMemberByCity(cities);
  8. for (Member m: members) {
  9. System.out.println(m);
  10. }
  11. }

十三、模糊查询

案例:根据昵称查询会员信息(模糊匹配 like)

13.1 模糊查询实现

13.1.1 DAO
  1. public interface MemberDAO {
  2. //根据昵称查询用户信息——模糊查询
  3. // 模糊查询需要使用${}取值,与sql进行拼接
  4. // 在使用${}时,即使只有一个参数也需要使用@Param注解声明参数的key(非String对象参数可以不用声明)
  5. public List<Member> searchMemberByNick(@Param("keyWord") String keyWord);
  6. }

13.1.2 映射文件
  1. <!--如果参数时String类型,需要parameterType声明参数类型-->
  2. <select id="searchMemberByNick" parameterType="java.lang.String" resultMap="memberMap">
  3. select member_id,member_nick,member_gender,member_age,member_city
  4. from members
  5. where member_nick like '%${keyWord}%'
  6. </select>

13.1.3 测试
  1. @Test
  2. public void testSearchMemberByNick(){
  3. MemberDAO memberDAO = MyBatisUtil.getMapper(MemberDAO.class);
  4. List<Member> members = memberDAO.searchMemberByNick("花");
  5. for (Member m: members) {
  6. System.out.println(m);
  7. }
  8. }

13.2 #{}和${}的区别

  • ${key} 表示获取参数,先获取参数的值拼接到SQL语句中,再编译执行SQL语句;可能引起SQL注入问题
  • {key} 表示获取参数,先完成SQL编译(预编译),预编译之后再将获取的参数设置到SQL与中 ,可以避免SQL注入问题

十四、MyBatis日志配置

MyBatis做为一个封装好的ORM框架,其运行过程我们没办法跟踪,为了让开发者了解MyBatis执行流程及每个执行步骤所完成的工作,MyBatis框架本身支持log4j日志框架,对运行的过程进行跟踪记录。我们只需对MyBatis进行相关的日志配置,就可以看到MyBatis运行过程中的日志信息。

14.1 添加日志框架依赖

  1. <!-- https://mvnrepository.com/artifact/log4j/log4j -->
  2. <dependency>
  3. <groupId>log4j</groupId>
  4. <artifactId>log4j</artifactId>
  5. <version>1.2.17</version>
  6. </dependency>

14.2 添加日志配置文件

  • 在resources目录下创建名为 log4j.properties文件
  • log4j.properties文件配置日志输出的方式
  1. # 声明日志的输出级别及输出方式
  2. log4j.rootLogger=DEBUG,stdout
  3. # MyBatis logging configuration...
  4. log4j.logger.org.mybatis.example.BlogMapper=TRACE
  5. # Console output...
  6. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  7. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  8. # 定义日志的打印格式 %t 表示线程名称 %5p 日志级别 %msg日志信息
  9. log4j.appender.stdout.layout.ConversionPattern=[%t] %5p - %msg \:%m%n

14.3 日志信息的级别

在使用日志框架输出日志信息的时候,会根据输出的日志信息的重要程度分为5个级别

级别 说明
DEBUG 输出调试信息
INFO 输出提示信息
WARN 输出警告信息
ERROR 一般性错误信息
FATAL 致命性错误信息

十五、配置数据库连接池-整合Druid

MyBatis做为一个ORM框架,在进行数据库操作时是需要和数据库建立连接的,MyBatis支持基于数据库连接池的连接创建方式。

当我们配置MyBatis数据源时,只要配置了dataSource标签的type属性值为POOLED时,就可以使用MyBatis内置的连接池管理连接。

如果我们想要使用第三方的数据库连接池,则需进行自定义配置。

15.1 常见的连接池

  • DBCP
  • C3P0
  • Druid 性能也比较好,提供了比较便捷的监控系统
  • Hikari 性能最好 | 功能 | dbcp | druid | c3p0 | HikariCP | | —- | —- | —- | —- | —- | | 是否支持PSCache | 是 | 是 | 是 | 否 | | 监控 | jmx | jmx/log/http | jmx,log | jmx | | 扩展性 | 弱 | 好 | 弱 | 弱 | | sql拦截及解析 | 无 | 支持 | 无 | 无 | | 代码 | 简单 | 中等 | 复杂 | 简单 | | 更新时间 | 2015.8.6 | 2015.10.10 | 2015.12.09 | 2015.12.3 | | 特点 | 依赖于common-pool | 阿里开源,功能全面 | 历史久远,代码逻辑复杂,且不易维护 | 优化力度大,功能简单,起源于boneCP | | 连接池管理 | LinkedBlockingDeque | 数组 | | threadlocal+CopyOnWriteArrayList |

15.2 添加Druid依赖

  1. <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
  2. <dependency>
  3. <groupId>com.alibaba</groupId>
  4. <artifactId>druid</artifactId>
  5. <version>1.2.5</version>
  6. </dependency>

15.3 创建Druid连接池工厂

  1. public class DruidDataSourceFactory extends PooledDataSourceFactory {
  2. public DruidDataSourceFactory() {
  3. this.dataSource = new DruidDataSource();
  4. }
  5. }

15.4 将DruidDataSourceFactory配置给MyBatis数据源

  1. <environments default="mysql">
  2. <environment id="mysql">
  3. <transactionManager type="JDBC"></transactionManager>
  4. <!-- POOLED 使用MyBatis内置的连接池实现 -->
  5. <!-- mybatis需要一个连接池工厂,这个工厂可以产生数据库连接池 PooledDataSourceFactory -->
  6. <dataSource type="com.qfedu.utils.DruidDataSourceFactory">
  7. <property name="driverClass" value="${driver}"/>
  8. <property name="jdbcUrl" value="${url}"/>
  9. <property name="username" value="${username}"/>
  10. <property name="password" value="${password}"/>
  11. </dataSource>
  12. </environment>
  13. </environments>

十六、MyBatis缓存

MyBatis是基于JDBC的封装,使数据库操作更加便捷;MyBatis除了对JDBC操作步骤进行封装之外也对其性能进行了优化:

  • 在MyBatis引入缓存机制,用于提升MyBatis的检索效率
  • 在MyBatis引入延迟加载机制,用于减少对数据库不必要的访问

16.1 缓存的工作原理

缓存,就是存储数据的内存

image.png

16.2 MyBatis缓存

MyBatis缓存分为一级缓存和二级缓存

16.2.1 一级缓存

一级缓存也叫做SqlSession级缓存,为每个SqlSession单独分配的缓存内存,无需手动开启可直接使用;多个SqlSession的缓存是不共享的。

特性:

1.如果多次查询使用的是同一个SqlSession对象,则第一次查询之后数据会存放到缓存,后续的查询则直接访问缓存中存储的数据;

2.如果第一次查询完成之后,对查询出的对象进行修改(此修改会影响到缓存),第二次查询会直接访问缓存,造成第二次查询的结果与数据库不一致;

3.当我们进行在查询时想要跳过缓存直接查询数据库,则可以通过sqlSession.clearCache();来清除当前SqlSession的缓存;

4.如果第一次查询之后第二查询之前,使用当前的sqlsession执行了修改操作,此修改操作会使第一次查询并缓存的数据失效,因此第二次查询会再次访问数据库。

测试代码:

  1. @Test
  2. public void testQueryMemberById(){
  3. SqlSession sqlSession1 = MyBatisUtil.getSqlSessionFactory().openSession();
  4. SqlSession sqlSession2 = MyBatisUtil.getSqlSessionFactory().openSession();
  5. MemberDAO memberDAO1 = sqlSession1.getMapper(MemberDAO.class);
  6. Member member1 = memberDAO1.queryMemberById(1);
  7. System.out.println(member1);
  8. member1.setMemberAge(99);
  9. sqlSession1.clearCache();
  10. System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  11. MemberDAO memberDAO2 = sqlSession1.getMapper(MemberDAO.class);
  12. Member member2 =memberDAO2.queryMemberById(1);
  13. System.out.println(member2);
  14. }

16.2.2 两次查询与数据库数据不一致问题

image.png

16.2.3 二级缓存

二级缓存也称为SqlSessionFactory级缓存,通过同一个factory对象获取的Sqlsession可以共享二级缓存;在应用服务器中SqlSessionFactory是单例的,因此我们二级缓存可以实现全局共享。

特性:

1.二级缓存默认没有开启,需要在mybatis-config.xml中的settings标签开启

2.二级缓存只能缓存实现序列化接口的对象

  • 在mybatis-config.xml开启使用二级缓存
  1. <settings>
  2. <setting name="cacheEnabled" value="true"/>
  3. </settings>
  • 在需要使用二级缓存的Mapper文件中配置cache标签使用功能二级缓存
  1. <cache/>
  • 被缓存的实体类实现序列化接口
  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @ToString
  5. public class Member implements Serializable {
  6. private int memberId;
  7. private String memberNick;
  8. private String memberGender;
  9. private int memberAge;
  10. private String memberCity;
  11. }
  • 测试
  1. @Test
  2. public void testQueryMemberById(){
  3. SqlSessionFactory factory =MyBatisUtil.getSqlSessionFactory();
  4. // 1.多个SqlSession对象必须来自于同一个SqlSessionFactory
  5. SqlSession sqlSession1 = factory.openSession(true);
  6. SqlSession sqlSession2 = factory.openSession(true);
  7. System.out.println(sqlSession1 == sqlSession2);
  8. MemberDAO memberDAO1 = sqlSession1.getMapper(MemberDAO.class);
  9. Member member1 = memberDAO1.queryMemberById(1);
  10. System.out.println(member1);
  11. sqlSession1.commit();
  12. //2.第一次查询之后执行sqlSession1.commit(),会将当前sqlsession的查询结果缓存到二级缓存
  13. System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  14. MemberDAO memberDAO2 = sqlSession2.getMapper(MemberDAO.class);
  15. Member member2 =memberDAO2.queryMemberById(1);
  16. System.out.println(member2);
  17. }

16.3 查询操作的缓存开关

  1. <select id="queryMemberById" resultMap="memberMap" useCache="false">
  2. select member_id,member_nick,member_gender,member_age,member_city
  3. from members
  4. where member_id=#{mid}
  5. </select>

十七、延迟加载

延迟加载——如果在MyBatis开启了延迟加载,在执行了子查询(至少查询两次及以上)时,默认只执行第一次查询,当用到子查询的查询结果时,才会触发子查询的执行;如果无需使用子查询结果,则子查询不会执行.

开启延迟加载:

  1. <resultMap id="classMap" type="Clazz">
  2. <id column="cid" property="classId"/>
  3. <result column="cname" property="className"/>
  4. <result column="cdesc" property="classDesc"/>
  5. <collection property="stus" select="com.qfedu.dao.StudentDAO.queryStudentsByCid" column="cid" fetchType="lazy"/>
  6. </resultMap>
  7. <select id="queryClassByCid" resultMap="classMap">
  8. select cid,cname,cdesc
  9. from classes
  10. where cid=#{cid}
  11. </select>

测试代码:

  1. @Test
  2. public void queryClassByCid() {
  3. ClassDAO classDAO = MyBatisUtil.getMapper(ClassDAO.class);
  4. Clazz clazz = classDAO.queryClassByCid(1);
  5. System.out.println(clazz.getClassName());
  6. System.out.println("-----------------------------------");
  7. System.out.println(clazz.getStus());
  8. }

运行日志:

image.png

练习任务

jsp+servlet+mybatis

商品信息的CRUD

  • 添加商品
  • 商品列表+分页
  • 删除商品
  • 修改商品
  • 商品详情