MyBatis特性

  1. MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  2. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old JavaObjects,普通的Java对象)映射成数据库中的记录
  4. MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

下载地址

搭建使用 MyBatis

导入依赖

在 maven 项目里导入 MyBatis 和 mysql

<!-- Mybatis核心 -->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.7</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.13</version>
</dependency>

创建全局配置文件

在项目 src/main/resources目录下创建 mybatis 配置文件,习惯上命名为 mybatis-config.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">

<configuration>
  <settings>
    <!--设置全局配置,将字段名的下划线自动映射为驼峰 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>
  <!-- 
   environments表示配置Mybatis的开发环境,
   可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。
   default属性的取值是environment标签的id属性的值。 
    -->
  <environments default="development">

    <!-- environment表示配置Mybatis的一个具体的环境 -->
    <environment id="development">
      <!-- Mybatis的内置的事务管理器 -->
      <transactionManager type="JDBC"/>
      <!-- 配置数据源 -->
      <dataSource type="POOLED">
        <!-- 建立数据库连接的具体信息 -->
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
    <!-- mapper标签:配置一个具体的Mapper映射文件 -->
    <!-- resource属性:指定Mapper映射文件的实际存储位置,
     这里需要使用一个以类路径根目录为基准的相对路径 -->
    <!-- 对Maven工程的目录结构来说,
    resources目录下的内容会直接放入类路径,
    所以这里我们可以以resources目录为基准 -->
    <mapper resource="mappers/EmployeeMapper.xml"/>
  </mappers>
</configuration>

创建映射配置文件

相关概念:ORM(Object Relationship Mapping)对象关系映射

  • 对象:Java的实体类对象

  • 关系:关系型数据库

  • 映射:二者之间的对应关系

java 类 -> 数据库表

java 属性 -> 字段/列

java 对象 -> 记录/行

创建数据库表和 java 类

CREATE DATABASE `mybatis-example`;

USE `mybatis-example`;

CREATE TABLE `t_emp`(
  emp_id INT AUTO_INCREMENT,
  emp_name CHAR(100),
  emp_salary DOUBLE(10,5),
  PRIMARY KEY(emp_id)
);

INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
  private Integer empId;

  private String empName;

  private Double empSalary;

}

创建 mapper 接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要 提供实现类

public interface EmployeeMapper {
  // 查询用户
  Employee selectEmployee(Integer empId);
}

创建对应的映射文件

映射文件的命名规则: 表所对应的实体类的类名+Mapper.xml

例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

因此一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置是``src/main/resources/mappers`目录下

MyBatis中可以面向接口操作数据,要保证两个一致:

  1. mapper接口的全类名和映射文件的命名空间(namespace)保持一致

  2. mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

加入配置文件 EmployeeMapper.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">

<!-- mapper是根标签,namespace属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 	引入接口后,为了方便通过接口全类名来找到Mapper配置文件,
   所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.example.mybatis.dao.EmployeeMapper">
  <!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
  <!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
  <select id="selectEmployee" resultType="com.example.mybatis.entity.Employee">
    <!-- Mybatis负责把SQL语句中的#{}部分替换成 “?” 占位符
  在#{}内部还是要声明一个见名知意的名称 -->
    select * from t_emp where emp_id=#{empId}
  </select>
</mapper>

测试功能

使用 junit 测试

@Test
public void test1() throws IOException {
  // 获取核心配置文件的输入流
  InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
  // 获取 sql SqlSessionFactoryBuilder 对象
  SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  // 获取 SqlSessionFactory 对象
  SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
  // 获取sql的会话对象SqlSession,是mybatis提供操作数据库的对象
  SqlSession sqlSession = sqlSessionFactory.openSession();
  // 获取UserMapper的代理实现对象
  EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
  // 调用 mapper接口中的方法,实现查询用户功能
  Employee employee = mapper.selectEmployee(1);

  sqlSession.commit();
  System.out.println(employee);
  sqlSession.close();
}

优化使用

加入日志功能

在Mybatis工作过程中,通过打印日志的方式,将要执行的SQL语句打印出来

<!-- 日志 -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
</dependency>

加入配置日志文件 logback.xml,与 mybatis 全局配置文件同路径

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
  <!-- 指定日志输出的位置,ConsoleAppender表示输出到控制台 -->
  <appender name="STDOUT"
            class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <!-- 日志输出的格式 -->
      <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
      <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
      <charset>UTF-8</charset>
    </encoder>
  </appender>

  <!-- 设置全局日志级别。日志级别按顺序分别是:TRACE、DEBUG、INFO、WARN、ERROR -->
  <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
  <root level="INFO">
    <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
    <appender-ref ref="STDOUT" />
  </root>

  <!-- 根据特殊需求指定局部日志级别 -->
  <logger name="com.example.mybatis" level="DEBUG" />

</configuration>

测试代码使用

在类上加上 @Slf4j 注解

使用logger对象打印数据

 log.info(result.toString());

外部文件配置数据库连接

新建 jdbc.properties

jdbc.dev.driver=com.mysql.jdbc.Driver
jdbc.dev.url=jdbc:mysql://localhost:3306/mybatis-example
jdbc.dev.username=root
jdbc.dev.password=123456
    
jdbc.test.driver=com.mysql.jdbc.Driver
jdbc.test.url=jdbc:mysql://localhost:3306/mybatis-example
jdbc.test.username=root
jdbc.test.password=123456
    
jdbc.product.driver=com.mysql.jdbc.Driver
jdbc.product.url=jdbc:mysql://localhost:3306/mybatis-example
jdbc.product.username=root
jdbc.product.password=123456

在 mybatis 全局配置文件引入 jdbc.properties

<properties resource="jdbc.properties"/>

读取外部文件配置的值

使用${key}格式引用属性文件中的键

<dataSource type="POOLED">
    <!-- 建立数据库连接的具体信息(引用了外部属性文件中的数据) -->
    <property name="driver" value="${jdbc.dev.driver}"/>
    <property name="url" value="${jdbc.dev.url}"/>
    <property name="username" value="${jdbc.dev.username}"/>
    <property name="password" value="${jdbc.dev.password}"/>
</dataSource>

添加测试 SqlSession

SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的 会话)

在测试类添加 SqlSession 方便测试

private SqlSession session;

// junit会在每一个@Test方法前执行@Before方法
@Before
public void init() throws IOException {
  session = new SqlSessionFactoryBuilder()
    .build(
    Resources.getResourceAsStream("mybatis-config.xml"))
    .openSession();
}

// junit会在每一个@Test方法后执行@After方法
@After
public void clear() {
  session.commit();
  session.close();
}

再次测试

@Slf4j
public class myBatisTest {
  private SqlSession session;
  // junit会在每一个@Test方法前执行@Before方法
  @Before
  public void init() throws IOException {
    session = new SqlSessionFactoryBuilder()
      .build(
      Resources.getResourceAsStream("mybatis-config.xml"))
      .openSession();
  }
  // junit会在每一个@Test方法后执行@After方法
  @After
  public void clear() {
    session.commit();
    session.close();
  }
  
  @Test
  public void test(){
    // 1.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    // 2.调用EmployeeMapper接口的方法完成对数据库的操作
    Employee emp = employeeMapper.selectEmployee(1);
    // 3.打印查询结果
    if (emp != null)
      log.info(emp.toString());
  }
}

基本用法

sql 参数

MyBatis获取参数值的两种方式:${}#{}

${}的本质就是字符串拼接,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号

#{} 的本质就是占位符赋值 ,字符串或日期会自动生成单引号

@Param标识参数

@Param注解标识 mapper 接口中的方法参数

此时,会将这些参数放在map集合中,以 @Param 注解的 value 属性值为键,以参数为值;

以 param1,param2…为键,以参数为值;

只需要通过 ${}#{}访问map集合的键就可以获取相对应的值

代码举例

查询一个用户

// mapper接口
User getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);-->
<select id="getUserById" resultType="User">
  select * from t_user where id = #{id}
</select>

查询一条数据为map集合

Map<String, Object> getUserToMap(@Param("id") int id);
<select id="getUserToMap" resultType="map">
  select * from t_user where id = #{id}
</select>

特殊 sql 执行

模糊查询

List<User> testMohu(@Param("mohu") String mohu);
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
  <!--select * from t_user where username like '%${mohu}%'-->
  <!--select * from t_user where username like concat('%',#{mohu},'%')-->
  select * from t_user where username like "%"#{mohu}"%"
</select>

批量删除

int deleteMore(@Param("ids") String ids);
<!--int deleteMore(@Param("ids") String ids);-->
<delete id="deleteMore">
  delete from t_user where id in (${ids})
</delete>

动态设置表名

List<User> getAllUser(@Param("tableName") String tableName);
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User">
  select * from ${tableName}
</select>

添加功能获取自增的主键

int insertUser(User user);
<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
  insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>
  • useGeneratedKeys:设置使用自增的主键
  • keyProperty:因为增删改有统一的返回值是受影响的行数,因此将获取的自增的主键放在传输的参数 user 对象的某个属性中

自定义映射

resultMap

若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射

属性:

  • id:表示自定义映射的唯一标识
  • type:查询的数据要映射的实体类的类型

子标签:

  • id:设置主键的映射关系
  • result:设置普通字段的映射关系
  • association:设置多对一的映射关系
  • collection:设置一对多的映射关系

子标签属性:

  • property:设置映射关系中实体类中的属性名
  • column:设置映射关系中表中的字段名

例如数据库字段名风格为_下划线,java 实体类使用驼峰:user_name -> userName

mapper 映射文件

<resultMap id="UserResMap" type="org.example.pojo.User">
  <id property="id" column="id" />
  <result property="userName" column="user_name" />
</resultMap>
<select id="getUserById" resultMap="UserResMap" >
  select id,user_name from t_user where id = #{id}
</select>

可以在MyBatis的核心配置文件中设置一个全局配置信息mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰

<setting name="mapUnderscoreToCamelCase" value="true"/>

多对一映射处理

使用场景:例如,有两张表,一张员工表,一张员工部门表,查询员工信息时,也查询该员工所对应的部门

java 实体类

@Data
public class Employee {
  private Integer empId;

  private String empName;

  private Double empSalary;

  private Dept dept;


}
// ----
@Data
public class Dept {
  private int did;
  private String dName;
}

级联方式处理映射关系

<resultMap id="EmpAndEept" type="org.example.pojo.Employee">
  <id property="empId" column="emp_id"/>
  <result property="empName" column="emp_name"/>
  <result property="empSalary" column="emp_salary"/>
  <result property="dept.did" column="did" />
  <result property="dept.dName" column="d_name"/>
</resultMap>
<!--Employee getEmpAndEept(@Param("id") int id);-->
<select id="getEmpAndEept" resultMap="EmpAndEept">
  select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.emp_id = #{id}
</select>

使用association处理映射关系

<resultMap id="EmpAndEept" type="org.example.pojo.Employee">
  <id property="empId" column="emp_id"/>
  <result property="empName" column="emp_name"/>
  <result property="empSalary" column="emp_salary"/>
  <association property="dept" javaType="org.example.pojo.Dept">
    <id property="did" column="did"/>
    <result property="dName" column="d_name"/>
  </association>

</resultMap>
<!--Employee getEmpAndEept(@Param("id") int id);-->
<select id="getEmpAndEept" resultMap="EmpAndEept">
  select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.emp_id = #{id}
</select>

分步查询

第一步:查询员工信息

Employee getEmpByIdOne(@Param("id") int id);

第二步:查询部门信息

Dept getDeptByIdTwo(@Param("did") int did);

第一步 mapper 映射

<!--  分步查询  -->
<resultMap id="EmpAndEeptByStep" type="org.example.pojo.Employee">
  <id property="empId" column="emp_id"/>
  <result property="empName" column="emp_name"/>
  <result property="empSalary" column="emp_salary"/>
  <association property="dept" select="org.example.mappers.DeptMapper.getDeptByIdTwo" column="did"/>
</resultMap>
<!-- Employee getEmpByIdOne(@Param("id") int id);-->
<select id="getEmpByIdOne" resultMap="EmpAndEeptByStep">
  select * from t_emp where emp_id = #{id}
</select>

association 标签属性

  • select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
  • column:将sql以及查询结果中的某个字段设置为分步查询的条件

第二步 mapper 映射

<resultMap id="DeptMap" type="org.example.pojo.Dept">
  <id property="did" column="did"/>
  <result property="dName" column="d_name"/>
</resultMap>
// 上面 resultMap 可开转驼峰配置省略

<!--Dept getDeptByIdTwo(@Param("did") int did);-->
<select id="getDeptByIdTwo" resultMap="DeptMap">
  select * from  t_dept where did = #{did}
</select>

总结:

  • 第一步查询 association 标签来设置分步查询
  • select 属性设置下一步的 mapper 接口
  • column 属性设置将该步查询的字段作为下一步查询所需的条件

一对多映射处理

使用场景:例如,查询部门信息,及部门中的员工信息

collection 标签

实体类加入员工信息

private List<Employee> emps;

mapper 接口

Dept getDeptEmpByDid(@Param("did") int did);

mapper 映射

<resultMap id="deptMap" type="org.example.pojo.Dept">
  <id property="did" column="did"/>
  <result property="dName" column="d_name"/>
  <collection property="emps" ofType="org.example.pojo.Employee">
    <id property="empId" column="emp_id"/>
    <result property="empName" column="emp_name"/>
    <result property="empSalary" column="emp_salary"/>
  </collection>
</resultMap>
<select id="getDeptEmpByDid" resultMap="deptMap">
  select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}
</select>

ofType:设置 collection 标签所处理的集合属性中存储数据的类型

分步查询

第一步:查询部门信息

Dept getDeptByStep(@Param("did") int did);

第二步:根据部门id查询部门中的所有员工

List<Emp> getEmpListByDid(@Param("did") int did);

第一步:mapper 映射文件

<resultMap id="deptEmpStep" type="org.example.pojo.Dept">
  <id property="did" column="did" />
  <result property="dName" column="d_name" />
  <collection
    property="emp"
    fetchType="eager"
    select="org.example.mappers.EmployeeMapper.getEmpListByDid"
    column="did"
  />
 
  </resultMap>
  <!--Dept getDeptByStep(@Param("did") int did);-->
  <select id="getDeptByStep" resultMap="deptEmpStep">
  select * from t_dept where did = #{did}
</select>

第二步:mapper 映射文件

<!--List<Emp> getEmpListByDid(@Param("did") int did);-->
<!-- 配置文件开启 mapUnderscoreToCamelCase了,使用resultType指定实体类即可 -->
<select id="getEmpListByDid" resultType="org.example.pojo.Employee">
  select * from t_emp where did = #{did}
</select>

动态 sql

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了
解决 拼接SQL语句字符串时的痛点问题

if 和 where

if 标签:i通过test属性的表达式判断,若结果为true,则标签中的内容会执行;反之不会执行

where 标签: 会自动去掉 “标签体内前面多余的 and/or

<!--  Employee selectByEmp(@Param("employee") Employee employee); -->
<select id="selectByEmp" resultType="org.example.pojo.Employee">
  select * from t_emp
  <where>
    <if test="employee.empName != '' and employee.empName != null" >
      emp_name = #{employee.empName}
    </if>
    <if test="employee.age != '' and employee.age != null">
      and age = #{employee.age}
    </if>
    <if test="employee.empSalary > 0">
      and emp_salary = #{employee.empSalary}
    </if>
  </where>
</select>

执行的 sql :select * from t_emp WHERE emp_name = ? and age = ? and emp_salary = ?

注:

  • 在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段
  • where 标签不能去掉条件最后多余的and
  • 使用 @Param(“employee”) 注解 ,在标签使用属性需要 employee.属性,不用可直接用属性,empName != ''

trim

trim 常用属性:

  • prefix:在trim标签中的内容的前面添加某些内容
  • prefixOverrides:在trim标签中的内容的前面去掉某些内容
  • suffix:在trim标签中的内容的后面添加某些内容
  • suffixOverrides:在trim标签中的内容的后面去掉某些内容

所以上面 where 也可以用 trim 代替

<!--  Employee selectByEmp(@Param("employee") Employee employee); -->
<select id="selectByEmp" resultType="org.example.pojo.Employee">
  select * from t_emp
  <trim prefix="where" suffixOverrides="and">
    <if test="employee.empName != '' and employee.empName != null" >
      emp_name = #{employee.empName}
    </if>
    <if test="employee.age != '' and employee.age != null">
      and age = #{employee.age}
    </if>
    <if test="employee.empSalary > 0">
      and emp_salary = #{employee.empSalary}
    </if>
  </trim>
</select>

set

对 update 语句的set子句进行定制,防止 null 值去更新表

例如根据 id 去更新名字和年龄

<!--   void UpdateEmp(Employee employee); -->
<update id="UpdateEmp">
  update t_emp
  <set>
    <if test="empName != null and empName !=''">
      emp_name = #{empName},
    </if>
    <if test="age != null and age !=''">
      age = #{age},
    </if>
  </set>
  where emp_id = #{empId}
</update>

执行的 sql :update t_emp SET emp_name = ?, age = ? where emp_id = ?

使用set标签动态管理set子句,并且动态去掉两端多余的逗号

choose、when、otherwise

choose、when、 otherwise相当于if…else if…else

<!-- List<Employee> selectEmp(Employee employee);-->
<select id="selectEmp" resultType="org.example.pojo.Employee">
  select * from t_emp
  <where>
    <choose>
      <when test="empName != null and empName !=''">
        emp_name = #{empName}
      </when>
      <when test="age != null and age !='' ">
        age = #{age}
      </when>
      <otherwise>
        1=1
      </otherwise>
    </choose>
  </where>

执行 sql

  • 名字不为空和 null :select * from t_emp WHERE emp_name = ?

  • 名字为空 ,年龄不为空:select * from t_emp WHERE age = ?

  • 名字和年龄为空: select * from t_emp WHERE 1=1

总结:

  • 在多个分支条件中,仅执行一个
  • 从上到下依次执行条件判断
  • 遇到的第一个满足条件的分支会被采纳
  • 被采纳分支后面的分支都将不被考虑
  • 如果所有的when分支都不满足,那么就执行otherwise分支

foreach

foreach 标签属性:

  • collection:遍历的集合
  • item:遍历集合的过程中能得到每一个具体对象
  • separator:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
  • open:整个循环把字符串拼好后,字符串整体的前面要添加的字符串
  • close:整个循环把字符串拼好后,字符串整体的后面要添加的字符串
  • index:List集合的索引值 / Map集合的key
<!--    int insertMoreEmp(@Param("employees") List<Employee> employees);-->
<insert id="insertMoreEmp">
  insert into t_emp values
  <foreach collection="employees" item="emp" separator=",">
    (null,#{emp.empName},#{emp.empSalary},#{emp.age},null,null)
  </foreach>
</insert>

执行的 sql :insert into t_emp values (null,?,?,?,null,null) ,(null,?,?,?,null,null) , (null,?,?,?,null,null)

SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过 include 标签进行引入

<sql id="empColumns">
id,empName,empSalary,age,did
</sql>
<!--使用-->
select <include refid="empColumns"></include> from t_emp

缓存

查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

MyBatis的一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession,查询的数据会被缓存

一级缓存失效的情况:

  • 不是同一个SqlSession
  • 同一个SqlSession但是查询条件发生了变化
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存
  • 同一个SqlSession两次查询期间提交了事务

MyBatis的二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存

开启二级缓存:

加入 cache 标签

<mapper namespace="com.example.mybatis.EmployeeMapper">
  <!-- 加入cache标签启用二级缓存功能 -->
  <cache/>

实体类加入序列化

public class Employee implements Serializable {

MyBatis缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. SqlSession 关闭之后,一级缓存中的数据会写入二级缓存

TDDO:整合EHCache

配置文件常用配置

开启别名

<!-- 配置类型的别名 -->
<typeAliases>
  <!-- 声明了实体类所在的包之后,在Mapper配置文件中,只需要指定这个包下的简单类名即可 -->
  <package name="com.example.mybatis.pojo"/>
</typeAliases>

例如:resultType=“Employee” 不用写全类名

<!-- Employee selectEmployeeById(Integer empId); -->
<select id="selectEmployeeById" resultType="Employee">
    select * from t_emp where emp_id=#{empId}
</select>

mybatis 内置别名

配置 mapper 映射

指定 Mapper 映射文件时,只指定其所在的包,即指定所有的映射文件

<mappers>
    <package name="com.example.mybatis.mapper"/>
</mappers>

要求:

  • Mapper 接口和 Mapper 配置文件名称一致
  • Mapper 配置文件所在目录的结构和 Mapper 接口所在包的目录结构一致
    • 例如接口文件在 java/com/example/mybatis/mapper/,映射文件就要在 resources/com/example/mybatis/mapper/

下划线映射驼峰

全局配置文件加入

<settings>
  <!--设置全局配置,将字段名的下划线自动映射为驼峰 -->
  <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

参考资料