【Mybatis】(一)
阅读原文时间:2023年07月15日阅读:2

Mybatis简介

提供持久层框架包括SQL Maps和Data Access Objects(DAO).

SQL Maps提供数据库数据和java数据的映射关系,换句话说即是封装JDBC的过程。

Data Access Objects数据访问对象,通过JDBC访问数据库然后操作数据库中的数据

Mybatis特性

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

高级映射指的是数据库字段名和java属性名不一致时的映射,相反只能处理相同情况的映射称为普通映射

2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java

Objects,普通的Java对象)映射成数据库中的记录

4) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

和其他持久层技术的对比

①JDBC

SQL 夹杂在Java代码中耦合度高,导致硬编码内伤

维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见

代码冗长,开发效率低

②Hibernate 和 JPA

操作简便,开发效率高

程序中的长难复杂 SQL 需要绕过框架

内部自动生产的 SQL,不容易做特殊优化

基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。

反射操作太多,导致数据库性能下降

MyBatis

轻量级,性能出色

SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

开发效率稍逊于HIbernate,但是完全能够接受

搭建Mybatis

开发环境

IDE:idea 2020.2

构建工具:maven 3.6.3

MySQL版本:MySQL 5.0.12

MyBatis版本:MyBatis 3.5.7

创建Maven工程

a>打包方式:jar

b>引入依赖

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-mxj-db-files</artifactId>
            <version>5.0.12</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
    </dependencies>

配置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>
    <!--配置数据库的环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件 -->
    <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>
</configuration>

创建mapper接口 UserMapper

mapper接口相当于以前的DAO,但是区别在于mapper仅仅是接口,不需要提供额外的实现类,因为mybatis里有面向接口编程,每当调用接口中的方法,mybatis就会自动执行相应的sql语句

public interface UserMapper {
    int insertUser();
}

创建同名映射文件 UserMapper.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.hikaru.mybatis.mapper.UserMapper">
    <insert id="insertUser">
        insert into user(user, balance) values("2b", 1000)
    </insert>
</mapper>

1、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

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

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

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

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

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

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

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

在mybatis核心配置文件中引入映射文件

<?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>
    <!--配置数据库的环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

测试:调用mapper接口方法

    @Test
    public void testMybatis() throws IOException {
        //加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //构建SqlSessionFactory
        SqlSessionFactory sessionFactory = builder.build(resourceAsStream);
        //获取SqlSession
        SqlSession sqlSession = sessionFactory.openSession();
        //返回一个接口对应的实现类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int result = mapper.insertUser();
        sqlSession.commit();
        System.out.println("result = " + result);
    }

SqlSession是一个数据库与java程序的通话,底层实现了mapper的接口实现类

当mapper接口调用方法时,mybatis会到同名配置文件执行被调用方法的相应sql语句

由于mybatis核心配置文件中的 sql的执行本质是事务,因此需要进行事务的提交

设置事务自动提交
SqlSession sqlSession = sessionFactory.openSession(true);

对Mybatis功能的优化 添加log4j日志

添加依赖
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
加入log4j的配置文件

log4j的配置文件为log4j.xml,文件目录为src/main/resource下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
%m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>

日志的级别

FATAL>ERROR>WARN>INFO>DEBUG

从左往右打印的内容会越来越详细

Mybatis实现增删改查

UserMapper.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.hikaru.mybatis.mapper.UserMapper">
    <insert id="insertUser">
        insert into user(user, balance) values("2b", 1000)
    </insert>

    <select id="getUserById" resultType="com.hikaru.mybatis.pojo.User">
        select * from user where id = 3
    </select>

    <select id="getAllUser" resultType="com.hikaru.mybatis.pojo.User">
        select * from user;
    </select>
</mapper>

查询功能必须设置resultType或resultMap

resultType:设置默认的映射关系,要求数据库字段名与实体属性名一致

resultMap:设置自定义的映射关系

Mapper接口

public interface UserMapper {
    int insertUser();
    User getUserById();
    List<User> getAllUser();
}

测试

    @Test
    public void testMybatis() throws IOException {
        //加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        //获取SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //构建SqlSessionFactory
        SqlSessionFactory sessionFactory = builder.build(resourceAsStream);
        //获取SqlSession
        SqlSession sqlSession = sessionFactory.openSession(true);
        //返回一个接口对应的实现类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById();
        System.out.println(user);
        List<User> users =mapper.getAllUser();
        users.forEach(user1 -> System.out.println(user1));
    }

核心配置文件详解

核心配置文件中的标签必须按照固定的顺序:

properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?

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>
    <!--配置数据库的环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件 -->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

enviroments

default:表示存在多个数据库环境时,默认使用的数据库环境id

enviroment

①id:数据库环境的唯一表示

②transactionManager:设置事务管理方式。

属性:type=“JDBC”或者“MANAGED”。JDBC表示是原生的事务管理方式,事物的提交和回滚都需要手动进行。MANAGED:被管理,例如Spring

③dataSource:配置数据源

属性type=“POOLED”或者“UNPOOLED”或者“JNDI”。POOLED表示数据库连接池,UNPOOLED表示不使用数据库连接池,JNDI表示使用上下文的数据源

在Spring整合后,可以直接使用Spring的数据源

使用properties存储JDBC数据库连接信息

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
jdbc.username=root
jdbc.password=abc123

在配置文件中使用标签的形式以及${}获取properties文件的键值对

    <properties resource="jdbc.properties"/>
    <!--配置数据库的环境 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

typeAliases package添加类型别名

    <typeAliases >
        <typeAlias type="com.hikaru.mybatis.pojo.User" alias="user"></typeAlias>
    </typeAliases>

属性type为被添加别名的类型,alias为添加的别名(不区分大小写

如没有声明alias属性,则默认别名为类名(仍不区分大小写

则在mybatis的范围内使用user及其大小写可以替换完整类名

<?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.hikaru.mybatis.mapper.UserMapper">
    <insert id="insertUser">
        insert into user(user, balance) values("2b", 1000)
    </insert>

    <select id="getUserById" resultType="user">
        select * from user where id = 3
    </select>

    <select id="getAllUser" resultType="User">
        select * from user;
    </select>
</mapper>

也可以使用标签aliases下的标签package,替换一个包内的所有类型名为默认别名

    <typeAliases >
        <package name="com.hikaru.mybatis.pojo"/>
    </typeAliases>

以包的形式添加mapper mappers>package

    <!--引入映射文件 -->
    <mappers>
<!--        <mapper resource="mappers/UserMapper.xml"/>-->
        <package name="com.hikaru.mybatis.mapper"/>
    </mappers>

要求映射文件的包名要和mapper接口所在包名一致

映射文件名和mapper接口名一致

资源下的包名不能用.要用/

Mybatis获取参数值的两种方式

①单个字面量的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号

        <!--select * from user where user = #{aaa}-->
        select * from user where user = '${aaa}'

②多个字面量类型的参数

Mapper接口:

int insertUserByNameAndBalance(String user, double balance);

Mapper映射文件:

    <insert id="insertUserByNameAndBalance">
        insert into user(user, balance) values (#{param1}, #{arg1})
    </insert>

若mapper接口中的方法参数为多个时

此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;以param1,param2…为键,以参数为值; 因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

③参数为Map集合类型的数据

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

mapper接口:

int insertUserByNameAndBalance(Map<String, Object> map);

mapper映射文件:

    <insert id="insertUserByNameAndBalance">
        insert into user(user, balance) values (#{user}, #{balance})
    </insert>

这时原来自定义的键值param和arg都不能再用了

④实体类型的参数

若mapper接口中的方法参数为实体类对象时

此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号

mapper接口

int insertUser(User user);

mapper映射文件

    <insert id="insertUser">
        insert into user(user, balance) values(#{user}, #{balance})
    </insert>

这里的属性名狭义上讲是实体类get、set方法的名称得到小写,而不是成员变量,要严格对应get、set方法的名称

⑤使用@Param标识参数

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

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

param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

mapper接口

int insertUser(@Param("username") String username, @Param("balance") double balance);

mapper映射文件

    <insert id="insertUser">
        insert into user(user, balance) values(#{username}, #{balance})
    </insert>


    <insert id="insertUser">
        insert into user(user, balance) values(#{param1}, #{param2})
    </insert>

这里相较于③map集合的方式,保留了param的键值对

Mybatis的各种查询功能

查询出的数据只有一条时,可以通过 ①实体类②list集合③map集合 接收

map集合形式为:{balance=1000, id=1, user=2b},这种方法方便将数据转换为json的形式

查询的数据有多条时,一定不能通过实体类对象接收,否则会报TooManyResultException错误

查询单个数据

mapper接口

int getCount();

mapper映射文件

    <select id="getCount" resultType="int">
        select count(*) from user;
    </select>


Alias                               Mapped Type
_byte                               byte
_long                               long
_short                               short
_int                               int
_integer                               int
_double                               double
_float                               float
_boolean                               boolean
string                               String
byte                               Byte
long                               Long
short                               Short
int                               Integer
integer                               Integer
double                               Double
float                               Float
boolean                               Boolean
date                               Date
decimal                               BigDecimal
bigdecimal                               BigDecimal
object                               Object
map                               Map
hashmap                               HashMap
list                               List
arraylist                               ArrayList
collection                               Collection
iterator                               Iteratot
collection
iterator

Mybatis内置的别名

Map类型查询结果

单个数据结果转换为Map

map接口:

Map<String, Object> getUserIdToMap(@Param("id") int id);

map映射文件:

    <select id="getUserIdToMap" resultType="map">
        select *
        from user
        where id = #{id};
    </select>
多个数据结果转换为Map

①通过List的形式

mapper接口

List<Map<String, Object>> list = mapper.getUserIdToMap();

mapper配置文件

<select id="getUserIdToMap" resultType="map">
    select *
    from user;
</select>

结果为map类型的list集合:

[{balance=1000, id=1, user=2b}, {balance=1000, id=2, user=9s}, {balance=1000, id=3, user=9s},

②通过@MapKey()的形式

mapper接口

    @MapKey("id")
    Map<String, Object> getUserIdToMap();

mapper映射文件

    <select id="getUserIdToMap" resultType="map">
        select *
        from user;
    </select>

结果为map类型的map,键为多个map获取的某一属性值,值为获取的多个map集合:

{1={balance=1000, id=1, user=2b}, 2={balance=1000, id=2, user=9s}, 3={balance=1000, id=3, user=9s},

Mybatis特殊查询

模糊查询
批量删除 in

mapper接口

int deleteMore(@Param("ids") String ids);

mapper映射文件

<delete id="deleteMore">
    delete
    from user
    where id in (${ids})
</delete>

in批量删除只能使用$,#的话会自动添加双引号导致sql语句不正确

动态获取表名

mapper接口

List<User> seleceByTableName(@Param("tableName") String tableName);

mapper配置文件

<select id="seleceByTableName" resultType="user">
    select *
    from ${tableName}

只能使用${}

添加功能获取自增的主键

mapper接口

int deleteMore(@Param("ids") String ids);

mapper配置文件

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user(user, balance) values(#{user}, #{balance})
</insert>

userGeneratedKeys设置当前标签中的sql是否使用了主键自增

keyProperty将自增的主键赋值给传输到配置文件的参数的某个属性

test

@Test
public void testMybatis() throws IOException {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User("ryuu", 10000, null);
    mapper.insertUser(user);
    System.out.println(user);
}

输出

User{user='ryuu', balance=10000.0, id=14}

解决多对一和一对多映射

”对一对应对象,对多对应集合“

自定义映射resultMap

字段名和属性名不一致的输出

Emp{eid=1, empName='null', sex='男', email='123@qq.com', did=1}
Emp{eid=2, empName='null', sex='女', email='123@qq.com', did=2}
Emp{eid=3, empName='null', sex='男', email='123@qq.com', did=3}
Emp{eid=4, empName='null', sex='女', email='123@qq.com', did=1}
Emp{eid=5, empName='null', sex='男', email='123@qq.com', did=2}

mapUnderscoreToCamelCase表示将数据库字段名_映射为java驼峰式属性名,解决字段名和属性名的映射关系

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
通过级联复制解决多对一的映射关系

mapper接口

List<Emp> getEmpAndDept(String eid);

mapper映射文件

<resultMap id="empAndDeptResultMap1" type="emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <result property="dept.did" column="did"></result>
    <result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap1">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>

测试结果

Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
通过association解决多对一映射

mapper接口

List<Emp> getEmpAndDept2(String eid);

mapper配置文件

<resultMap id="empAndDeptResultMap2" type="emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <association property="dept" javaType="dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
    </association>
</resultMap>
<select id="getEmpAndDept2" resultMap="empAndDeptResultMap2">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>

测试结果

Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
分布查询解决多对一的映射关系

mapper接口

List<Emp> getEmpAndDeptStep1(@Param("eid") String eid);


Dept getEmpAndDeptStep2(String did);

mapper映射文件

<resultMap id="empAndDeptResultMap" type="emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <association property="dept"
                 select="com.hikaru.mybatis.mapper.DeptMapper.getEmpAndDeptStep2"
                 column="did">
    </association>
</resultMap>
<select id="getEmpAndDeptStep1" resultMap="empAndDeptResultMap">
    select * from t_emp where eid = #{eid};
</select>

select表示调用其他表对应的映射接口的方法

<select id="getEmpAndDeptStep2" resultType="dept">
    select * from t_dept where did = #{did};
</select>

这里没有对dept映射做ResultMap处理,是因为mybatis核心配置中开启了下划线到驼峰的自动映射

结果

Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=Dept{did=2, deptName='B'}}
分布查询的延迟加载 lazyLoadingEnabled
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

理解延迟加载:并不是开启后只会执行第一步的查询而不执行第二步,而是需要执行第二步的时候才去执行第二步

测试①:

Emp emp = empMapper.getEmpAndDeptStep1("2");
System.out.println(emp.getEmpName());

输出:

DEBUG 05-04 13:04:48,410 ==>  Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:04:48,437 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:04:48,495 <==      Total: 1 (BaseJdbcLogger.java:137)
2B

测试②:

Emp emp = empMapper.getEmpAndDeptStep1("2");
System.out.println(emp.getDept());

输出:

DEBUG 05-04 13:06:45,414 ==>  Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,446 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,567 <==      Total: 1 (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,569 ==>  Preparing: select * from t_dept where did = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,569 ==> Parameters: 2(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:06:45,571 <==      Total: 1 (BaseJdbcLogger.java:137)
Dept{did=2, deptName='B'}

如上第二组测试中需要查询第二步SQL的时候,延迟加载才会去执行第二步

fetchType标签单独设置延迟加载

fetchType值为eager或者lazy

<association property="dept"
             select="com.hikaru.mybatis.mapper.DeptMapper.getEmpAndDeptStep2"
             column="did"
             fetchType="eager">
</association>

测试输出:

DEBUG 05-04 13:11:48,340 ==>  Preparing: select * from t_emp where eid = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,365 ==> Parameters: 2(String) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,383 ====>  Preparing: select * from t_dept where did = ?; (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,384 ====> Parameters: 2(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,385 <====      Total: 1 (BaseJdbcLogger.java:137)
DEBUG 05-04 13:11:48,387 <==      Total: 1 (BaseJdbcLogger.java:137)
2B

解决多对一的映射关系

”对一对应对象,对多对应集合“

collection解决多对一的映射关系

mapper接口

Dept getDeptAndEmp(@Param("did") String did);

mapper映射文件

<resultMap id="deptAndEmpResultMap" type="dept">
    <id property="did" column="did"></id>
    <result property="deptName" column="dept_name"></result>
    <collection property="emps" ofType="emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
    </collection>
</resultMap>
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
    select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did};
</select>

collection标签用于解决多对一的映射关系,与association需要javatype获取对象类型不同,collection需要ofType获取集合内的对象类型

集合内对象不能再声明Dept类,防止套娃

通过分布查询解决多对一映射

mapper接口

Dept getDeptAndEMmpStep1(@Param("did") String did);


List<Emp> getDeptAndEmpStep2(@Param("did") String did);

mapper映射文件

<resultMap id="deptAndEMmpStep1RS" type="dept">
    <id property="did" column="did"></id>
    <id property="deptName" column="dept_name"></id>
    <collection property="emps"
                select="com.hikaru.mybatis.mapper.EmpMapper.getDeptAndEmpStep2"
                column="did"></collection>
</resultMap>
<select id="getDeptAndEMmpStep1" resultMap="deptAndEMmpStep1RS">
    select * from t_dept where did = #{did}
</select>


<select id="getDeptAndEmpStep2" resultType="emp">
    select *
    from t_emp where did = #{did};
</select>

动态SQL

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

if标签

mapper接口

Emp getEmpByCondition(Emp emp);

mapper映射文件

<select id="getEmpByCondition" resultType="emp">
    select * from t_emp where 1 = 1
    <if test="empName != null and empName != ''">
        and emp_name = #{empName}
    </if>
    <if test="sex != null and sex != ''">
        and sex = #{sex}
    </if>
    <if test="email != null and email != ''">
        and email = #{email}
    </if>
    <if test="age != null and age != ''">
        and age = #{age}
    </if>
</select>

使用恒成立条件1=1防止sql语句and前缀错误

测试输出

DEBUG 05-05 09:11:48,799 ==>  Preparing: select * from t_emp where 1 = 1 and emp_name = ? and sex = ? and email = ? (BaseJdbcLogger.java:137)
DEBUG 05-05 09:11:48,824 ==> Parameters: 1A(String), 男(String), 123@qq.com(String) (BaseJdbcLogger.java:137)
DEBUG 05-05 09:11:48,847 <==      Total: 1 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', sex='男', email='123@qq.com', age='23', dept=null}

Process finished with exit code 0

where标签

mapper映射文件

<select id="getEmpByCondition" resultType="emp">
    select * from t_emp
    <where>
        <if test="empName != null and empName != ''">
            and emp_name = #{empName}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
    </where>
</select>

当where标签中有内容时,会自动生成where关键字,并将内容前多余的and、or去掉

当where标签中没有内容时,where标签不会生成where关键字

where标签不能将内容后面的and、or去掉

trim标签

trim若标签中有内容时:

prefix|suffix:将trim标签中内容前面或者后面添加指定的内容

prefixOverrides|suffixOverrides:将trim标签中内容前面或者后面去掉指定的内容

若标签中没有内容:trim标签也没有任何的效果

mapper映射文件

<select id="getEmpByCondition" resultType="emp">
    select * from t_emp
    <trim prefix="where" suffixOverrides="and">
        <if test="empName != null and empName != ''">
            emp_name = #{empName} and
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex} and
        </if>
        <if test="email != null and email != ''">
            email = #{email} and
        </if>
        <if test="age != null and age != ''">
            age = #{age} and
        </if>
    </trim>
</select>

输出

DEBUG 05-05 09:36:20,141 ==>  Preparing: select * from t_emp (BaseJdbcLogger.java:137)
DEBUG 05-05 09:36:20,172 ==> Parameters:  (BaseJdbcLogger.java:137)
DEBUG 05-05 09:36:20,196 <==      Total: 5 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', sex='男', email='123@qq.com', age='23', dept=null}
Emp{eid=2, empName='2B', sex='女', email='123@qq.com', age='22', dept=null}
Emp{eid=3, empName='3S', sex='男', email='123@qq.com', age='23', dept=null}
Emp{eid=4, empName='9S', sex='女', email='123@qq.com', age='24', dept=null}
Emp{eid=5, empName='1T', sex='男', email='123@qq.com', age='22', dept=null}

choose:when、otherwise

when、otherwise相当于if if if … else

foreach

属性collection表示需要循环的数组或集合,item为数组或集合中的每一个数据

separator表示循环体之间的分隔符

open|close可以在标签中的内容的开始和结束添加指定内容

foreach实现批量删除

mapper接口

int deleteMore(@Param("eids") Integer[] ids);

mapper映射文件

<delete id="deleteMore">
    delete from t_emp where eid in
    <foreach collection="eids" separator="," item="eid" open="(" close=")">
        #{eid}
    </foreach>
</delete>

测试输出

DEBUG 05-05 10:11:59,303 ==>  Preparing: delete from t_emp where eid in ( ? , ? ) (BaseJdbcLogger.java:137)
DEBUG 05-05 10:11:59,329 ==> Parameters: 6(Integer), 7(Integer) (BaseJdbcLogger.java:137)
DEBUG 05-05 10:11:59,331 <==    Updates: 0 (BaseJdbcLogger.java:137)
foreach实现批量添加

mapper接口

int insertMore(@Param("emps") List<Emp> emps);

mapper配置文件

<insert id="insertMore">
    insert into t_emp (<include refid="empColumns"/>) values
    <foreach collection="emps" item="emp" separator=",">
        (null, #{emp.empName}, #{emp.sex}, #{emp.email},  1)
    </foreach>
</insert>

这里的include是引用sql片段

设置sql片段

<sql id="empColumns">eid, emp_name, sex, email, did</sql>

测试

@Test
public void testDynamicSQLMapper() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class)
      //Integer eid, String empName, String sex, String email, Integer did
    Emp emp1 = new Emp(null, "a1", "男", "123@qq.com");
    Emp emp2 = new Emp(null, "a2", "男", "123@qq.com");
    Emp emp3 = new Emp(null, "a3", "男", "123@qq.com");
    List<Emp> emps = Arrays.asList(emp1, emp2, emp3);
    int result = mapper.insertMore(emps);
    System.out.println(result);
}

Mybatis缓存

缓存只适用于查询

Mybatis一级缓存

一级缓存是sqlSession级别,自动开启

令一级缓存失效的情况:

①不同的SqlSession对应不同的一级缓存

②同一个SqlSession但是查询条件不同

③同一个SqlSession两次查询期间执行了任何一次增删改操作

④同一个SqlSession两次查询期间手动清空了缓存,sqlsession.clearCache()

测试

SqlSession sqlSession = SqlSessionUtils.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
List<Emp> emps = mapper.getAll();
emps.forEach(emp -> System.out.println(emp));
SqlSession sqlSession1 = SqlSessionUtils.getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
List<Emp> emps1 = mapper1.getAll();
emps1.forEach(emp -> System.out.println(emp));

结果为两次sql查询

DEBUG 05-05 11:20:12,191 ==>  Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,213 ==> Parameters:  (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,231 <==      Total: 11 (BaseJdbcLogger.java:137
DEBUG 05-05 11:20:12,297 ==>  Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,297 ==> Parameters:  (BaseJdbcLogger.java:137)
DEBUG 05-05 11:20:12,300 <==      Total: 11 (BaseJdbcLogger.java:137) )

Mybatis二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询(即使SqlSession不同)的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启的条件:

a> 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="cacheEnabled" value="true"/>
</settings>

b> 在mapper映射文件中设置标签cache

<cache />

c> 二级缓存必须在SqlSession关闭或提交之后有效

d> 查询的数据所转换的实体类类型必须实现序列化的接口

即是使实体类继承Serializable接口

使二级缓存失效的情况:

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

测试

@Test
public void testCacheTwo() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = builder.build(resourceAsStream);
    SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
    List<Emp> emps1 = mapper1.getAll();
    emps1.forEach(emp -> System.out.println(emp));
    sqlSession1.close();
    CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
    List<Emp> emps2 = mapper2.getAll();
    emps2.forEach(emp -> System.out.println(emp));
    sqlSession2.close();
}

测试结果:

DEBUG 05-06 10:30:39,916 Cache Hit Ratio [com.hikaru.mybatis.mapper.CacheMapper]: 0.0 (LoggingCache.java:60)
DEBUG 05-06 10:30:40,800 ==>  Preparing: select * from t_emp; (BaseJdbcLogger.java:137)
DEBUG 05-06 10:30:40,822 ==> Parameters:  (BaseJdbcLogger.java:137)
DEBUG 05-06 10:30:40,846 <==      Total: 11 (BaseJdbcLogger.java:13
7)

DEBUG 05-06 10:34:31,559 Cache Hit Ratio [com.hikaru.mybatis.mapper.CacheMapper]: 0.5 (LoggingCache.java:60)

可以看到只查询了一次SQL,第二次缓存利用率为0.5

Mybatis二级缓存的相关配置

eviction属性:缓存回收策略

缓存是缓存到内存中的,故不能无限制地缓存,因此需要设定策略定期清除

LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。

    FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。

    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

   默认的是 LRU。

flushInterval属性:刷新间隔,单位毫秒

    默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用增删改语句时刷新

size属性:引用数目,正整数

    代表缓存最多可以存储多少个对象,太大容易导致内存溢出

readOnly属性:只读,true/false

    true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

    false:读写缓存;会返回缓存对象的拷贝通过序列化)。这会慢一些,但是安全,因此默认是false

MyBatis缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据(因为二级缓存对应多个SQLSession,范围更大),可以拿来直接使用。

如果二级缓存没有命中,再查询一级缓存

如果一级缓存也没有命中,则查询数据库

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

第三方缓存EHCache

a>添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

b>各jar包功能

jar包名称                                             作用

mybatis-ehcache                 Mybatis和EHCache的整合包

ehcache                                         EHCache核心包

slf4j-api SLF4J                                    日志门面包

logback-classic                 支持SLF4J门面接口的一个具体实

c>创建EHCache的配置文件ehcache.xml

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

d>设置二级缓存的类型

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

e>加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。

创建logback的配置文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -
->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

f>EHCache配置文件说明

![](file://D:\资料\学习笔记\Java\Mybatis\1.png?msec=1651885414653)

Mybatis逆向工程

Simple版本(只有增删改查五个功能)

正向工程: 先创建实体类,再由框架生成数据库表。Hibernate支持正向工程

逆向工程: 先创建数据库表,然后由框架生成下面的资源:

①实体类

②Mapper接口

③Mapper映射文件

a 添加依赖和插件

<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.16</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

b 创建MyBatis核心文件

c 创建逆向工程的配置文件 generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
    MyBatis3: 生成带条件的CRUD(奢华尊享版)
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mydb"
                        userId="root"
                        password="abc123">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.hikaru.mybatis.pojo"
                            targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.hikaru.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.hikaru.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>

其中enableSubPackages属性声明是否使用子包(设为false的话,com.hikaru.mybatis.pojo就会被当成一整个路径名而不是多个)

trimStrings属性可以将数据库字段名前后的空格去掉,方便生成资源

d>执行MBG插件的generate目标

![](file://D:\资料\学习笔记\Java\Mybatis\2.png?msec=1651885414566)

测试

DEBUG 05-06 12:23:44,119 ==>  Preparing: select eid, emp_name, age, sex, email, did from t_emp (BaseJdbcLogger.java:137)
DEBUG 05-06 12:23:44,143 ==> Parameters:  (BaseJdbcLogger.java:137)
DEBUG 05-06 12:23:44,167 <==      Total: 11 (BaseJdbcLogger.java:137)
Emp{eid=1, empName='1A', age='23', sex='男', email='123@qq.com', did=1}
Emp{eid=2, empName='2B', age='22', sex='女', email='123@qq.com', did=2}

在执行过程中出现了数据库连接文件JBDC.properties以及log4j无法加载的问题,找了半天错,原来是target文件中没有这两个文件,在Maven中重新clean compile一下即可

高级版本(可实现条件CUID)

a 修改逆向工程核心配置文件的版本号

<context id="DB2Tables" targetRuntime="MyBatis3">

b 生成实体类Example,用于条件CURD 其余与Simple版本相同

c

测试查询、主键查询和条件查询
@Test
public void testGenerate() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.
    SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().b
    SqlSession sqlSession = sqlFactory.openSession(true);
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    //  List<Emp> emps = mapper.selectByExample(null);
    //  emps.forEach(emp -> System.out.println(emp));
    EmpExample empExample = new EmpExample();
    empExample.createCriteria().andAgeLike("%21%");
    empExample.or().andEidEqualTo(3);
    empExample.or().andEidEqualTo(4);
    List<Emp> emps = mapper.selectByExample(empExample);
    emps.forEach(emp -> System.out.println(emp));
}

输出SQL语句

Preparing: select eid, emp_name, age, sex, email, did from t_emp WHERE ( age like ? ) or( eid = ? ) or( eid = ? ) (BaseJdbcLogger.java:137)
Parameters: %21%(String), 3(Integer), 4(Integer) (BaseJdbcLogger.java:137)
Total: 2 (BaseJdbcLogger.java:137) 7)
测试两种修改、删除
    @Test
    public void testGenerate2() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlFactory.openSession(true);

        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//        (Integer eid, String empName, String age, String sex, String email, Integer did) {
//        int result = mapper.updateByPrimaryKey(new Emp(1, "1A", null, "男", "456@qq.com", 2));
        int result = mapper.updateByPrimaryKeySelective(new Emp(1, "1A", null, "男", "456@qq.com", 2));
        System.out.println(result);

    }

直接修改会将null也跟新到数据库字段中,而selective选择性修改碰到null则不会对数据库字段做任何修改