Mybatis学习

19 阅读6分钟

MyBatis 3 | 入门 – mybatis

映射文件

两个一致:

方法名和sql语句中的id一致

映射文件名字和Mapper接口名字一致

  1. 加载 MyBatis 配置文件

InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

  • Resources.getResourceAsStream(): MyBatis 提供的工具方法

  • 从 classpath 根目录加载 mybatis-config.xml 配置文件

  • 返回配置文件的输入流

  1. 创建 SqlSessionFactoryBuilder

SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  • 创建工厂构建器,用于构建 SqlSessionFactory
  1. 构建 SqlSessionFactory

SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);

  • 根据配置文件构建 SqlSessionFactory(数据库会话工厂)

  • 这是重量级对象,通常应用生命周期内只创建一次

  • 负责解析配置、创建数据库连接等

  1. 获取 SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

  • 创建 SqlSession(数据库会话)

  • 这是轻量级对象,每次数据库操作都应该创建新的

  • 默认情况下不会自动提交事务,需要则在openSession中设置true

  1. 获取 Mapper 接口代理对象

UserMapper u = sqlSession.getMapper(UserMapper.class);

  • 通过动态代理技术创建 UserMapper 接口的实现类

  • MyBatis 会自动将接口方法与对应的 SQL 映射关联

  1. 执行数据库操作

int result = u.insertUser(); System.out.println(result);

  • 调用 Mapper 方法执行 SQL

  • insertUser() 对应 XML 或注解中的 SQL 语句

  • 返回受影响的行数

resulttype和resultmap

resulttype主要用于定义java类型,数据库字段和实体类属性名一致

resultmap主要用于映射配置,若数据库字段和实体类属性名不一致,定义映射,使得实体类中的

属性可以接收数据库的数据

MyBatis获取参数值的两种方式

${}:字符串拼接 #{}:占位符赋值

当mapper接口的参数为多个时,Mybatis会将这些参数放在map集合当中

以ang0,arg1...为键

以param1,param2....为键

或者直接定义一个map集合,手动将数据放到map中,根据键值访问数据

@Test  
public void test3() throws IOException {  
    SqlSession s =SqlSessionUtils.getSqlSession();  
    UserMapper userMapper=s.getMapper(UserMapper.class);  
    Map<String,Object> map=new HashMap<>();  
    map.put("param1","wss");  
    map.put("param2","1234");  
  
    User user=userMapper.checkLogin(map);  
    log.info("user:{}",user);  
}
<select id="checkLogin" resultType="User">  
    select * from user where username=#{param1} and password=#{param2}  
</select>

使用@Param注解命名参数,Mybatis将这些参数放在一个Map集合中,以两种方式存储 利用#{}的方式访问键中的值

Mybatis查询功能

查询数据只有一条: 可以用实体类接收 可以用List数组接收 可以用map集合接收

查询数据多条: 不能使用实体类接收

@MapKey注解

如果在查询多条数据时,想要用Map类型的数据返回,需要在Mapper接口处加上@MapKey注解,指明返回数据的键值,键值与返回数据的字段名相关

模糊查询

使用${}进行查询

select * from user where username like '%${username}%'

使用concat字符串连接

select * from user where username like concat(‘%’,#{username},'%')

批量删除

delete from user where id in (${ids})

添加功能获取自定义主键

<insert id="addUser" useGeneratedKeys="true" keyProperty="id">  
    insert into user values(null,#{username},#{password},#{age},#{sex},#{email})  
</insert>

成员变量和属性详细对比

方面成员变量 (Field)属性 (Property)
本质变量声明方法(getter/setter)
语法private String name;getName(), setName()
访问控制直接通过访问修饰符控制通过方法的公开性控制
封装性直接暴露实现细节隐藏实现,提供接口
计算能力只是存储数据可以在方法中添加逻辑
命名规范无特殊要求getter/setter遵循JavaBean规范
  1. 所有属性都对应着方法,但不一定有对应的成员变量

  2. 有成员变量不一定有对应的属性(如果是private且没有getter/setter)

  3. 属性是面向外部的接口,成员变量是内部实现

  4. 良好的设计应该:成员变量私有,通过属性方法对外提供访问

思考:为什么数据库列名要与实体类属性名进行映射,而参数不用

传递参数时,参数已经在方法调用栈中,可以直接通过参数名或者参数位置进行访问

// MyBatis内部简化逻辑(参数解析器)
public class ParamNameResolver {
    public Object getNamedParams(Object[] args) {
        // 参数直接存储在args数组中
        // 创建Map: {username=value1, age=value2, param1=value1, param2=value2}
        Map<String, Object> param = new HashMap<>();
        param.put("username", args[0]);    // 通过参数名访问
        param.put("age", args[1]);         // 通过参数名访问
        param.put("param1", args[0]);      // 通过位置访问
        param.put("param2", args[1]);      // 通过位置访问
        return param;
    }
}

实体类传递时,由于成员变量是私有属性,Mybatis通过反射进行访问,获取指定的get方法,如果没有进行映射的话,Mybatis无法找到对应的get方法,也就无法进行值的传递

// 这些写法必须严格匹配属性名
<select id="findUser" resultType="User">
    select * from user 
    where username = #{username}      <!-- 正确:对应getUsername() -->
    <!-- where username = #{user} -->     <!-- 错误:没有getUser()方法 -->
    <!-- where username = #{name} -->     <!-- 错误:没有getName()方法 -->
</select>

ResultMap使用

基础语法

<resultMap id="唯一标识" type="映射的Java类型">
    <!-- 主键映射 -->
    <id column="数据库列名" property="Java属性名"/>
    <!-- 普通字段映射 -->
    <result column="数据库列名" property="Java属性名"/>
    <!-- 关联对象映射 -->
    <association property="Java属性名" javaType="关联对象类型"/>
    <!-- 集合映射 -->
    <collection property="Java属性名" ofType="集合元素类型"/>
</resultMap>

<select id="selectUserById" resultMap="BaseUserMap">
    select user_id, user_name, age, email, create_time
    from user 
    where user_id = #{userId}
</select>

处理映射关系

多对一:

级联属性赋值 一次性获取所有属性,建立映射

<resultMap id="EmployeeAndDeptMap" type="Employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    
    <!-- 级联属性赋值 -->
    <result column="dept_id" property="dept.id"/>
    <result column="department_name" property="dept.departmentName"/>
</resultMap>

<select id="getEmployeeAndDeptById" resultMap="EmployeeAndDeptMap">
    select 
        e.id, e.last_name, e.email, e.dept_id,
        d.id as dept_id, d.department_name
    from employee e
    left join department d on e.dept_id = d.id
    where e.id = #{id}
</select>

association

<!-- 方式1:嵌套结果映射 -->
<resultMap id="EmployeeWithDeptMap" type="Employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    
    <!-- association 关联映射 -->
    <association property="dept" javaType="Department">
        <id column="dept_id" property="id"/>
        <result column="department_name" property="departmentName"/>
    </association>
</resultMap>

<select id="getEmployeeWithDept" resultMap="EmployeeWithDeptMap">
    select 
        e.id, e.last_name, e.email,
        d.id as dept_id, d.department_name
    from employee e
    left join department d on e.dept_id = d.id
    where e.id = #{id}
</select>

分布查询 Mapper接口

// EmployeeMapper.java
public interface EmployeeMapper {
    // 级联属性方式
    Employee getEmployeeAndDeptById(Integer id);
    
    // association方式
    Employee getEmployeeWithDept(Integer id);
    
    // 分步查询方式
    Employee getEmployeeByIdStep(Integer id);
}

// DepartmentMapper.java
public interface DepartmentMapper {
    Department getDeptById(Integer id);
}

SQL映射文件

<!-- 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="com.example.mapper.EmployeeMapper">

    <!-- 1. 级联属性方式 -->
    <resultMap id="EmployeeAndDeptMap" type="Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="dept_id" property="dept.id"/>
        <result column="department_name" property="dept.departmentName"/>
    </resultMap>

    <!-- 2. association方式 -->
    <resultMap id="EmployeeWithDeptMap" type="Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <association property="dept" javaType="Department">
            <id column="dept_id" property="id"/>
            <result column="department_name" property="departmentName"/>
        </association>
    </resultMap>

    <!-- 3. 分步查询方式 -->
    <resultMap id="EmployeeStepMap" type="Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="email" property="email"/>
        <result column="dept_id" property="deptId"/>
        <association property="dept" 
                     select="com.example.mapper.DepartmentMapper.getDeptById"
                     column="dept_id"
                     fetchType="lazy"/>
    </resultMap>

    <!-- 查询方法 -->
    <select id="getEmployeeAndDeptById" resultMap="EmployeeAndDeptMap">
        select e.id, e.last_name, e.email, e.dept_id,
               d.id as dept_id, d.department_name
        from employee e
        left join department d on e.dept_id = d.id
        where e.id = #{id}
    </select>

    <select id="getEmployeeWithDept" resultMap="EmployeeWithDeptMap">
        select e.id, e.last_name, e.email,
               d.id as dept_id, d.department_name
        from employee e
        left join department d on e.dept_id = d.id
        where e.id = #{id}
    </select>

    <select id="getEmployeeByIdStep" resultMap="EmployeeStepMap">
        select id, last_name, email, dept_id
        from employee 
        where id = #{id}
    </select>

</mapper>

<!-- DepartmentMapper.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="com.example.mapper.DepartmentMapper">

    <select id="getDeptById" resultType="Department">
        select id, department_name
        from department 
        where id = #{id}
    </select>

</mapper>
特性级联属性association分步查询
SQL次数1次1次2次
性能可能更好(懒加载)
配置复杂度简单中等复杂
灵活性最高
懒加载不支持不支持支持
适用场景简单关联复杂关联大型对象、性能敏感

一对多

collection:处理一对多的关系

ofType:该属性集合中的类型

<resultMap id="empResultMap" type="org.example.pojo.Emp">  
    <id column="id" property="id"/>  
    <result column="username" property="username"/>  
    <result column="password" property="password"/>  
    <result column="name" property="name"/>  
    <result column="gender" property="gender"/>  
    <result column="phone" property="phone"/>  
    <result column="job" property="job"/>  
    <result column="salary" property="salary" />  
    <result column="image" property="image"/>  
    <result column="entry_date" property="entryDate"/>  
    <result column="dept_id" property="deptId"/>  
    <result column="create_time" property="createTime"/>  
    <result column="update_time" property="updateTime"/>  
  
    <collection property="exprList" ofType="org.example.pojo.EmpExpr">  
        <id column="ee_id" property="id"/>  
        <result column="ee_empid" property="empId"/>  
        <result column="ee_begin" property="begin"/>  
        <result column="ee_end" property="end"/>  
        <result column="ee_company" property="company"/>  
        <result column="ee_job" property="job"/>  
    </collection></resultMap>

动态SQL

if where 标签

select emp.*,dept.name deptName from emp left join  dept on emp.dept_id = dept.id  
<where>  
    <if test="name !=null and name !=''">  
        emp.name like concat('%',#{name},'%')  
    </if>  
  <if test="gender !=null">  
      and emp.gender=#{gender}  
  </if>  
  <if test="begin !=null and end !=null">  
      and emp.entry_date between #{begin} and #{end}  
  </if>  
</where>

trim标签

prefix:在trim标签内sql语句加上前缀 suffix:在trim标签内sql语句加上后缀 prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND | OR”,去除trim标签内sql语句多余的前缀"and"或者"or"。 suffixOverrides:指定去除多余的后缀内容。

foreach标签

<!-- in查询所有,不分页 -->
<select id="selectIn" resultMap="BaseResultMap">
    select name,hobby from student where id in
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

collection:collection 属性的值有三个分别是 list、array、map 三种,分别对应的参数类型为:List、数组、map 集合。(如果传入单个参数,可以直接写参数名) item :表示在迭代过程中每一个元素的别名 index :表示在迭代过程中每次迭代到的位置(下标) open :前缀 close :后缀 separator :分隔符,表示迭代时每个元素之间以什么分隔

Mybatis缓存

Mybatis的一级缓存

级别:SqlSession

同一个SqlSession查询的数据被缓存,下一次查询时会从缓存中拿取数据

二级缓存

需要手动开启(默认开启)

在xml文件中设置标签开启二级缓存

在SqlSession关闭或提交后有效

查询的数据转化的实体类必须实现序列化的接口

查询顺序

先查询二级缓存,二级缓存中有其他程序查询出的数据

再查询一级缓存

数据库

SqlSession关闭,一级缓存中的数据会写入二级缓存

第三方缓存EHCaChe

引入依赖

<dependency>  
    <groupId>org.mybatis.caches</groupId>  
    <artifactId>mybatis-ehcache</artifactId>  
    <version>1.2.1</version>  
</dependency>
<dependency>  
    <groupId>ch.qos.logback</groupId>  
    <artifactId>logback-classic</artifactId>  
    <version>1.4.14</version>  
</dependency>

配置文件

<?xml version="1.0" encoding="UTF-8"?>  
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">  
    <diskStore path="java.io.tmpdir"/>  
    <!-- defaultCache -->  
    <defaultCache  
            maxElementsInMemory="10000"  
            eternal="false"  
            timeToIdleSeconds="120"  
            timeToLiveSeconds="120"  
            maxElementsOnDisk="10000000"  
            diskExpiryThreadIntervalSeconds="120"  
            memoryStoreEvictionPolicy="LRU">  
        <persistence strategy="localTempSwap"/>  
    </defaultCache>    <!-- userEntityCache(有效期5min) -->  
    <cache name="userEntityCache" maxElementsInMemory="10" eternal="false"  
           timeToIdleSeconds="60"  
           timeToLiveSeconds="300"  
           overflowToDisk="true"  
           memoryStoreEvictionPolicy="LRU"/>  
</ehcache>