mybatis学习笔记

开始

  1. 创建数据库mybatis,运行sql,创建表,这里使用的是mysql8

    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(30) DEFAULT NULL,
      `pwd` varchar(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    INSERT INTO `user`(`name`, `pwd`) VALUES ('hong', '123456');
    INSERT INTO `user`(`name`, `pwd`) VALUES ('Tom', '123456');
    INSERT INTO `user`(`name`, `pwd`) VALUES ('Jerry', '123456');
    

    表数据如下图

    image-20211014104203150

  2. 导入相关依赖

    <dependencies>
        <!-- MySQL驱动 -->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
        <!-- 日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    
        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    
  3. resource/xml路径下编写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="aphler.mapper.UserMapper">
        <select id="getAll" resultType="aphler.entity.User">
            select * from user
        </select>
    </mapper>
    
  4. 编写mybatis配置文件mybatis-config.xml,jdbc.propertieslog4j.properties在resource目录下

    <?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: 核心配置文件 -->
    <configuration>
    
        <!--
        properties: 引入外部properties文件 必须放在最前面,否则会报错
          resource: 类路径下
          url: 磁盘路径或网络路径
      -->
        <properties resource="jdbc.properties"/>
    
        <!-- 设置日志输出, 方便观察sql语句和参数 -->
        <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
    
        <!--
            environments配置项目的运行环境, 可以配置多个
            default: 启用的环境
         -->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!-- 数据库连接信息 -->
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 每一个Mapper.xml都需要在MyBatis核心配置文件中注册!!! -->
        <mappers>
            <mapper resource="xml/UserMapper.xml"/>
        </mappers>
    
    </configuration>
    
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username=root
    password=123456
    
    log4j.rootLogger=DEBUG,A1
    
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n
    
  5. 添加实体类UserUserMapper

    User:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
    
        private Integer id;
        private String name;
        private String pwd;
    }
    
    

    UserMapper

    public interface UserMapper {
    
        List<User> getAll();
    }
    
  6. 测试运行

    public class Main {
        public SqlSessionFactory getSqlSessionFactory() throws IOException {
            // MyBatis全局配置文件路径
            String resource = "mybatis-config.xml";
            // 获取MyBatis全局配置文件的输入流
            InputStream is = Resources.getResourceAsStream(resource);
            // 获取SqlSessionFactory对象
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
            return factory;
        }
    
        @Test
        public void testHello() throws IOException {
            // 1、获取SqlSessionFactory对象
            SqlSessionFactory factory = getSqlSessionFactory();
    
            // 2、获取SqlSession对象
            SqlSession openSession = factory.openSession();
            try {
                // 3、获取接口的实现类对象
                // 会为接口自动创建代理对象, 代理对象去执行增删改查方法, sql语句会从mapper.xml中获取
                UserMapper mapper = openSession.getMapper(UserMapper.class);
                List<User> list = mapper.getAll();
                list.forEach(System.out::println);
            } finally {
                // 4、SqlSession代表和数据库的一次对话, 用完必须关闭
                openSession.close();
            }
        }
    }
    

CRUD初体验

insert

  1. 创建MybatisUtils工具类

    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                // 获取sqlSessionFactory对象
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        // 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
        // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
        public static SqlSession getSqlSession() {
            // openSession(): 此方式打开SQL会话, 事务是开启状态
            // openSession(true): 此方式打开SQL会话, 事务是关闭状态
            return sqlSessionFactory.openSession();
        }
    
        public static SqlSessionFactory getSqlSessionFactory() {
            return sqlSessionFactory;
        }
    }
    
    
  2. 创建插入数据的方法

    //保存一个用户
    int save(User user);
    
  3. 编写方法映射

    <!--
        insert: 配置insert语句
        id: 对应的方法名
        parameterType: 指定参数类型为pojo, 可以直接写属性名获得属性值, 优先调用getting方法, 如果没有getting方法则直接从属性中取值 -->
    <insert id="save" parameterType="aphler.entity.User">
        insert into user(name, pwd) values(#{name}, #{pwd})
    </insert>
    
  4. 测试运行

    @Test
    public void save(){
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 3.调用对应的方法执行操作
        User user = new User();
        user.setName("save");
        user.setPwd("123");
        int save = mapper.save(user);
        System.out.println(save);
        System.out.println(user);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    

    数据已经插入到数据库中,并返回插入的行数:1

    控制台打印结果说明

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 1141113940.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@44040454]
    # 预编译的sql
    [main] [aphler.mapper.UserMapper.save]-[DEBUG] ==>  Preparing: insert into user(name, pwd) values(?, ?) 
    # 参数
    [main] [aphler.mapper.UserMapper.save]-[DEBUG] ==> Parameters: save(String), 123(String)
    # 影响的行数
    [main] [aphler.mapper.UserMapper.save]-[DEBUG] <==    Updates: 1
    1
    User(id=null, name=save, pwd=123)
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@44040454]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@44040454]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@44040454]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 1141113940 to pool.
    

delete

<delete id="delete">
    delete from user where id=#{id}
</delete>

update

<update id="update" parameterType="aphler.entity.User">
    update user set name=#{name},pwd=#{pwd} where id=#{id}
</update>

select

<select id="get" resultType="aphler.entity.User">
    select * from user where id=#{id}
</select>

总结

<?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="aphler.mapper.UserMapper">
    <select id="getAll" resultType="aphler.entity.User">
        select * from user
    </select>

    <!--
    insert: 配置insert语句
        id: 对应的方法名
        parameterType: 指定参数类型为pojo, 可以直接写属性名获得属性值, 优先调用getting方法, 如果没有getting方法则直接从属性中取值 -->
    <insert id="save" parameterType="aphler.entity.User">
        insert into user(name, pwd) values(#{name}, #{pwd})
    </insert>

    <select id="get" resultType="aphler.entity.User">
        select * from user where id=#{id}
    </select>

    <update id="update" parameterType="aphler.entity.User">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>

    <delete id="delete">
        delete from user where id=#{id}
    </delete>

</mapper>

insert主键值回填

获取自增主键的值

<!--
        useGeneratedKeys="true": 开启获取自增主键的策略
        keyColumn: 指定数据库主键的列名
        keyProperty: 指定对应的主键属性, ps(获取到主键值后, 将这个值封装给javaBean的哪个属性)
 -->
<insert id="save1" parameterType="aphler.entity.User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
    insert into user(name, pwd) values(#{name}, #{pwd})
</insert>
@Test
public void save1() {
    // 1.获取sqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    // 2.获取需要的mapper接口的代理对象
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 3.调用对应的方法执行操作
    User user = new User();
    user.setName("save1");
    user.setPwd("123");
    int save = mapper.save1(user);
    System.out.println(save);
    System.out.println(user);
    // 4.提交事务
    sqlSession.commit();
    // 5.关闭sqlSession
    sqlSession.close();
}

控制台打印,可以看到主键回填到了对象当中

[main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
[main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 1800649922.
[main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6b53bcc2]
[main] [aphler.mapper.UserMapper.save1]-[DEBUG] ==>  Preparing: insert into user(name, pwd) values(?, ?) 
[main] [aphler.mapper.UserMapper.save1]-[DEBUG] ==> Parameters: save1(String), 123(String)
[main] [aphler.mapper.UserMapper.save1]-[DEBUG] <==    Updates: 1
1
User(id=8, name=save1, pwd=123)
[main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6b53bcc2]
[main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6b53bcc2]
[main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6b53bcc2]
[main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 1800649922 to pool.

参数处理

单个参数

单个参数可以随便写

  • 方法

    //单个参数
    User getOneParam(int id);
    
  • 方法映射

    <!--    单个参数随便写-->
    <select id="getOneParam" resultType="aphler.entity.User">
        select * from user where id=#{sadkasdasd}
    </select>
    

多个参数

MyBatis会做特殊处理,多个参数会被封装成一个 map

key:param1...paramN or arg0...argN-1param从1开始,arg从0开始

​ **value:**传入的参数

接口方法

// 多个参数
User getParams(String name, String pwd);

方法映射

<select id="getParams" resultType="aphler.entity.User">
    <!-- 
    参数name可以使用 #{arg0} 或 #{param1} 取出
    参数pwd可以使用 #{arg1} 或 #{param2} 取出
    -->
    select * from user where name = #{arg0} and pwd = #{param2}
</select>

命名参数

使用注解 @Param 指定参数的 key

多个参数会被封装成一个map

  • **key:**使用 @param 注解给参数多加一个 key,原来的 argparam 依旧能使用

  • **value:**参数值

接口方法

//命名参数
User getAnnoParam(@Param("name") String name, String pwd);

方法映射

<select id="getAnnoParam" resultType="aphler.entity.User">
    select * from user where name=#{name} and pwd=#{arg1}
</select>

POJO

多个参数正好是业务逻辑的数据模型(实体类),直接传入pojo(对象)

#{属性名}: 取出传入的pojo对应属性的值

参考CRUD初体验部分的update

Map

如果多个参数不是业务模型中的数据模型, 没有对应的pojo, 可以传入map

#{key}: 取出map中key对应的值

接口方法

//参数是Map
User getMapParam(Map<String, String> map);

接口映射

<select id="getMapParam" resultType="aphler.entity.User">
    select * from user where name=#{name} and pwd=#{pwd}
</select>

#{} 和 ${} 的区别

#{}是占位符,${}是拼接符。

#{}是预编译处理,KaTeX parse error: Expected 'EOF', got '#' at position 22: …替换。 Mybatis 在处理#̲{}时,会将 sql 中的#{… {}时,就是把${}替换成变量的值。 使用#{}可以有效的防止 SQL 注入,提高系统安全性。

接口方法

// #{}和${}的区别
List<User> getOrderBy(String order);

方法映射

<!-- #{}和${}的区别 -->
<select id="getOrderBy" resultType="org.hong.pojo.User">
    select * from user order by id ${order}
</select>

测试用例

@Test
public void test$(){
    // 1.获取sqlSession对象
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    // 2.获取需要的mapper接口的代理对象
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 3.调用对应的方法执行操作
    List<User> desc = mapper.getOrderBy("desc");
    desc.forEach(System.out :: println);
    // 4.提交事务
    sqlSession.commit();
    // 5.关闭sqlSession
    sqlSession.close();
}

返回List和Map

List

  • 接口方法

    //返回List
    List<User> getUserList();
    
  • 方法映射

    <select id="getUserList" resultType="aphler.entity.User">
        select * from user;
    </select>
    

Map

key -> 列名,value -> 列值
  • 接口方法

    //返回Map
    Map<String, Object> getUserMap(int id);
    
  • 方法映射

    <select id="getUserMap" resultType="java.util.Map">
        select * from user where id=#{id}
    </select>
    
  • 测试用例

    @Test
    public void getMap() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> userMap = mapper.getUserMap(3);
        userMap.forEach((k, v) -> {
            System.out.println("key: " + k + " , value: " + v);
        });
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
  • 结果

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 1393828949.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@53142455]
    [main] [aphler.mapper.UserMapper.getUserMap]-[DEBUG] ==>  Preparing: select * from user where id=? 
    [main] [aphler.mapper.UserMapper.getUserMap]-[DEBUG] ==> Parameters: 3(Integer)
    [main] [aphler.mapper.UserMapper.getUserMap]-[DEBUG] <==      Total: 1
    key: name , value: Jerry
    key: id , value: 3
    key: pwd , value: 123456
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@53142455]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@53142455]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 1393828949 to pool.
    
key -> 主键,value -> 实体对象
  • 接口方法

    // 返回Map集合, key -> 主键值、value -> 对应的实体对象
    @MapKey("id")
    Map<Integer, User> getUserMap1();
    
  • 方法映射

    <select id="getUserMap1" resultType="java.util.Map">
        select * from user
    </select>
    
  • 测试用例

    @Test
    public void getMap1() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<Integer, User> map = mapper.getUserMap1();
        for (Integer key : map.keySet()) {
            System.out.println(key + "-->" + map.get(key));
        }
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
  • 结果打印

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 768776793.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2dd29a59]
    [main] [aphler.mapper.UserMapper.getUserMap1]-[DEBUG] ==>  Preparing: select * from user 
    [main] [aphler.mapper.UserMapper.getUserMap1]-[DEBUG] ==> Parameters: 
    [main] [aphler.mapper.UserMapper.getUserMap1]-[DEBUG] <==      Total: 7
    2-->{name=张三, id=2, pwd=123}
    3-->{name=Jerry, id=3, pwd=123456}
    4-->{name=save, id=4, pwd=123}
    5-->{name=save, id=5, pwd=123}
    6-->{name=save, id=6, pwd=123}
    7-->{name=save, id=7, pwd=123}
    8-->{name=save1, id=8, pwd=123}
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2dd29a59]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2dd29a59]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 768776793 to pool.
    

配置解析

核心配置文件(mybatis-config.xml)

MyBatis的配置文件包含了影响MyBatis行为的设置和属性信息

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

环境配置(environments)

MyBatis 可以配置成适应多种环境。不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

学会使用配置多套运行环境

MyBatis 默认的事务管理器就是JDBC,连接池:POOLED

<!-- default: 表示使用哪种环境,里面写环境的id -->
<environments default="mysql">
    <!-- MySql环境 -->
    <environment id="mysql">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="username" value="root"/>
            <property name="password" value="1234"/>
        </dataSource>
    </environment>
    <!-- Oracle环境 -->
    <environment id="oracle环境">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="Driver: com.ibm.db2.jdbc.app.DB2Driver"/>
            <property name="url" value="jdbc:db2://localhost:5000/orcl"/>
            <property name="username" value="scott"/>
            <property name="password" value="ccat"/>
        </dataSource>
    </environment>
</environments>

属性

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(db.properties)

编写一个 properties 配置文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
username=root
password=1234

在核心配置文件中引入

<!-- 
引入外部配置文件
	首先读取在 properties 元素体内指定的属性。
	然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
-->
<properties resource="db.properties">
    <property name="username" value="root"/>
</properties>

使用

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。存在的意义仅在于用来降低冗余的全限定类名书写
  • MyBatis中的别名是不区分大小写的
<!-- 可以给实体类起别名 -->
<typeAliases>
    <typeAlias type="pojo.User" alias="User"/>
</typeAliases>
  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean
    每一个在包 org.hong.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名,比如 org.hong.pojo.User 的别名为 user
<typeAliases>
    <package name="org.hong.pojo"/>
</typeAliases>
  • 若有注解,则别名为其注解值。通常用来解决不同包中相同类名的别名冲突
@Alias("author")
public class Author {
    ...
}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,

注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

设置(setting)

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods )。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falsefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
<settings>
    <!-- 开启驼峰命名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

映射器(mappers)

方式一

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/hong/mapper/UserMapper.xml"/>
</mappers>

方式二

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.hong.mapper.UserMapper"/>
</mappers>

方式三

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.hong.mapper"/>
</mappers>

方式二&方式三的注意点

  • 接口和对应的Mapper配置文件必须同名
  • 接口和对应的Mapper配置文件编译后必须在同一个文件夹下
    1. 可以把接口和对应的Mapper创建在同一个包下
    2. 可以在resources文件加中创建和接口相同层数并且名称相同的包,然后创建接口对应的Mapper

生命周期和作用域

生命周期和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder
  • 一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(局部变量)。
SqlSessionFactory
  • 可以想象为数据库连接池

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

  • 因此 SqlSessionFactory 的最佳作用域是应用作用域(Application)。

  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession
  • 想象为连接到连接池的一个请求

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域

  • 用完之后需要马上关闭,否则会导致资源被占用

日志

日志工厂

如果一个数据库操作出现了异常,我们需要排错。日志就是最好的助手!!!

曾经:sout,Debug

现在:日志工厂!

logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
  • SLF4J

  • LOG4J 【掌握】

  • LOG4J2

  • JDK_LOGGING

  • COMMONS_LOGGING

  • STDOUT_LOGGING 【掌握】

  • NO_LOGGING

在MyBatis中具体使用那个日志实现,在设置中设定!

STDOUT_LOGGING(标准日志输出)

在mybatis核心配置文件中,配置我们的日志

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 日志的重点部分 -->
Opening JDBC Connection
Created connection 1205555397.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@47db50c5]
==>  Preparing: select * from User 
==> Parameters: 
<==    Columns: id, name, pwd
<==        Row: 1, 张三, 123456
<==      Total: 1
User{id=1, name='张三', password='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@47db50c5]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@47db50c5]
Returned connection 1205555397 to pool.

LOG4J

什么是log4j?

  • Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

  • 我们也可以控制每一条日志的输出格式。

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  1. 先导入log4j的jar包
<dependencies>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>
  1. log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/hong.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 配置log4j为日志的实现
<settings>
    <setting name="logImpl" value="LOG4J"></setting>
</settings>
  1. Log4j的使用!,直接运行测试查询

简单使用

  1. 在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;
  2. 日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
  1. 日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");

注解开发

简单的sql语句使用注解,复杂的sql语句和结果集映射使用xml配置文件

insert

@Insert("insert into user(name,pwd) values (#{name},#{pwd})")
int save2(User user);

select

@Select("select * from user where id=#{id}")
User getUserList1(int id);

update

@Update("update user set name = #{name}, pwd = #{pwd} where id = #{id}")
int update(User user);

detele

@Delete("delete from user where id = #{id}")
int delete(int id);

resultMap(结果集映射)

自定义Java Bean封装规则

下面演示数据库字段与实体类属性名不一致的情况

  1. 新建表(student)

    /*
     Navicat Premium Data Transfer
    
     Source Server         : localhost
     Source Server Type    : MySQL
     Source Server Version : 80013
     Source Host           : localhost:3306
     Source Schema         : mybatis
    
     Target Server Type    : MySQL
     Target Server Version : 80013
     File Encoding         : 65001
    
     Date: 19/10/2021 15:14:23
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for student
    -- ----------------------------
    DROP TABLE IF EXISTS `student`;
    CREATE TABLE `student`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
      `age` int(11) NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of student
    -- ----------------------------
    INSERT INTO `student` VALUES (1, '张三', 12);
    INSERT INTO `student` VALUES (2, '李四', 14);
    INSERT INTO `student` VALUES (3, '王五', 11);
    
    SET FOREIGN_KEY_CHECKS = 1;
    

    表数据如下

    image-20211019151735045

  2. 创建实体类,且字段名与数据库中的不一致

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Student {
        private Integer id;
        private String stuName;
        private Integer stuAge;
    }
    
  3. 接口方法

    public interface StudentMapper {
        List<Student> getAll();
    }
    
  4. 接口映射

    <?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="aphler.mapper.StudentMapper">
    
        <!--
            resultMap: 自定义结果集映射规则
                id: 唯一标识, 方便被引用
                type: 映射到的实体类的全类名
         -->
        <resultMap id="student" type="aphler.entity.Student">
            <!--
                id: 映射实体类中与主键对应的属性
                    column: 主键列的列名
                    property: 主键列对应的属性
             -->
            <!-- result: 映射普通属性 -->
            <result property="stuName" column="name"></result>
            <result property="stuAge" column="age"></result>
        </resultMap>
        <!--
            如果javaBen属性名和列名不一致, 不应该再使用resultType配置返回值类型
            而是使用resultMap引用自定义的结果集映射规则
         -->
    
        <select id="getAll" resultMap="student">
            select * from student
        </select>
    
    </mapper>
    
  5. 测试用例

    @Test
    public void getAll() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getAll();
        studentList.forEach(System.out::println);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    

多对一与一对多

环境搭建

接下来演示一个部门对应多个员工,一个员工对应一个部门的关系映射。

  • 创建表

    CREATE TABLE `dept` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    INSERT INTO dept(`id`, `name`) VALUES (1, '开发部'); 
    INSERT INTO dept(`id`, `name`) VALUES (2, '测试部'); 
    
    CREATE TABLE `employee` (
      `id` INT(10) NOT NULL,
      `name` VARCHAR(30) DEFAULT NULL,
      `did` INT(10) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `fkdid` (`did`),
      CONSTRAINT `fkdid` FOREIGN KEY (`did`) REFERENCES `dept` (`id`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    INSERT INTO `employee` (`id`, `name`, `did`) VALUES ('1', '小明', '1'); 
    INSERT INTO `employee` (`id`, `name`, `did`) VALUES ('2', '小红', '2'); 
    INSERT INTO `employee` (`id`, `name`, `did`) VALUES ('3', '小张', '1'); 
    INSERT INTO `employee` (`id`, `name`, `did`) VALUES ('4', '小李', '2'); 
    INSERT INTO `employee` (`id`, `name`, `did`) VALUES ('5', '小王', '1');
    
  • 实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dept {
        private Integer id;
        private String name;
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Employee {
        private Integer id;
        private String name;
        private Integer did;
    }
    
  • 创建deptMapperEmployeeMapper

一对一

接下来要做的是:获取一个雇员的同时获取该雇员所在的部门

  • 实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Employee {
        private Integer id;
        private String name;
        // 多对一, 查询N方的同时获取到1方
        private Dept dept;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dept {
        private Integer id;
        private String name;
    }
    
联合查询
  • 接口方法

    获取所有的部门

    List<Employee> getAll();
    
  • 方法映射

    • DeptMapper.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="aphler.mapper.DeptMapper">
          <resultMap id="dept" type="aphler.entity.Dept">
              <id property="id" column="did"></id>
              <result property="name" column="dname"></result>
          </resultMap>
      </mapper>
      
    • 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="aphler.mapper.EmployeeMapper">
          <!-- 创建一个只封装普通属性的resultMap映射规则 -->
          <resultMap id="employee" type="aphler.entity.Employee">
              <id property="id" column="id"></id>
              <result property="name" column="name"></result>
          </resultMap>
      
          <!-- 方式一 联合查询, 级联属性封装结果-->
          <!-- 使用extends属性继承一个resultMap可以获得指定的resultMap定义过的映射规则, 就可以省略普通属性, 只写级联属性的规则 -->
          <resultMap id="employee1" type="aphler.entity.Employee" extends="employee">
              <result property="dept.id" column="did"></result>
              <!-- 两张表都有name字段, 在进行封装的时候就会出现问题, 要么查询的时候取别名, 要么在建表的时候就避免 -->
              <result property="dept.name" column="dname"></result>
          </resultMap>
      
          <!-- 方式二 给指定联合的javaBean对象编写映射规则
                      association:定义关联对象的封装规则
                          property: 指定哪个属性是联合的对象
                          javaType: 指定这个属性对象的类型[不能省略]-->
          <resultMap id="employee2" type="aphler.entity.Employee" extends="employee">
              <association property="dept" javaType="aphler.entity.Dept">
                  <id property="id" column="did"></id>
                  <result property="name" column="dname"></result>
              </association>
          </resultMap>
      
          <!-- 方式三: 使用association节点的resultMap属性指定级联对象的映射规则, 而不是再写一份 -->
          <resultMap id="employee3" type="aphler.entity.Employee" extends="employee">
              <association property="dept" resultMap="aphler.mapper.DeptMapper.dept"></association>
          </resultMap>
      
          <!-- 查询Employee的同时查询出对应Dept, 此时使用resultType就做不到了, 需要使用resultMap引用自自定义的映射规则 -->
          <select id="getAll" resultMap="employee2">
              <!-- 内连接 -->
              select e.*, d.id did, d.name dname from employee e inner join dept d on e.did = d.id
          </select>
      </mapper>
      
  • 测试用例

    @Test
    public void getAll() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<Employee> employeeList = mapper.getAll();
        employeeList.forEach(System.out::println);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
嵌套查询

先查询出雇员,再通过雇员的id获取所在的部门

  • 接口方法

    public interface DeptMapper {
    
        Dept get(Integer id);
    }
    
    public interface EmployeeMapper {
    
        List<Employee> getAll();
    }
    
  • 方法映射

    DeptMapper.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="aphler.mapper.DeptMapper">
        <select id="get" resultType="aphler.entity.Dept">
            select * from dept where id=#{id}
        </select>
    </mapper>
    

    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="aphler.mapper.EmployeeMapper">
    
        <!--    思路:
        先查询出员工的部门id,在根据部门id查询出部门-->
        <resultMap id="employee" type="aphler.entity.Employee">
            <id property="id" column="id"></id>
            <result property="name" column="name"></result>
            <!--
                association:定义关联对象的封装规则
                  select: 表明当前属性是调用select指定的方法查出的结果
                  column: 指定将那一列的值传给select
             -->
            <association property="dept" column="did" javaType="aphler.entity.Dept" select="aphler.mapper.DeptMapper.get"></association>
        </resultMap>
    
        <select id="getAll" resultMap="employee">
            select * from employee
        </select>
    
    </mapper>
    
一对多

接下来要做的是,获取一个部门的时候,将它旗下的所有的雇员查询出来

  • 实体类

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dept {
        private Integer id;
        private String name;
        private List<Employee> employeeList;
    }
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Employee {
        private Integer id;
        private String name;
    }
    
联合查询
  • 接口方法

    public interface DeptMapper {
        Dept get(Integer id);
    }
    
  • 方法映射

    <?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="aphler.mapper.EmployeeMapper">
    
        <resultMap id="employee" type="aphler.entity.Employee">
            <id property="id" column="eid"></id>
            <result property="name" column="name"></result>
        </resultMap>
    
    </mapper>
    
    <?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="aphler.mapper.DeptMapper">
        <resultMap id="dept" type="aphler.entity.Dept">
            <id property="id" column="id"></id>
            <result property="name" column="name"></result>
        </resultMap>
    
        <!--    方法一-->
        <resultMap id="dept1" type="aphler.entity.Dept" extends="dept">
            <collection property="employeeList" ofType="aphler.entity.Employee">
                <id property="id" column="eid"></id>
                <result property="name" column="name"></result>
            </collection>
        </resultMap>
    
        <!--    方法二-->
        <resultMap id="dept2" type="aphler.entity.Dept" extends="dept">
            <collection property="employeeList" ofType="aphler.entity.Employee" resultMap="aphler.mapper.EmployeeMapper.employee"></collection>
        </resultMap>
    
        <select id="get" resultMap="dept1">
            select dept.*,employee.id eid,employee.name ename from dept join employee on dept.id=employee.did where dept.id=#{id}
        </select>
        
    </mapper>
    
  • 测试用例

    @Test
    public void get() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = mapper.get(1);
        System.out.println(dept);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
嵌套查询

先查询出部门,再通过部门的id获取所有的雇员

  • 接口方法

    public interface EmployeeMapper {
        List<Employee> getByDid(Integer did);
    }
    
    public interface DeptMapper {
        Dept get(Integer id);
    }
    
  • 方法映射

    <?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="aphler.mapper.EmployeeMapper">
    
        <select id="getByDid" resultType="aphler.entity.Employee">
            select * from employee where did=#{did};
        </select>
    
    </mapper>
    
    <?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="aphler.mapper.DeptMapper">
        <resultMap id="dept" type="aphler.entity.Dept">
            <id property="id" column="id"></id>
            <result property="name" column="name"></result>
        </resultMap>
    
       <resultMap id="dept1" type="aphler.entity.Dept" extends="dept">
           <collection property="employeeList" column="id" select="aphler.mapper.EmployeeMapper.getByDid"></collection>
       </resultMap>
    
        <select id="get" resultMap="dept1">
            select * from dept where id=#{id}
        </select>
    
    </mapper>
    
  • 测试用例

    @Test
    public void get() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = mapper.get(1);
        System.out.println(dept);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    

懒加载

什么是懒加载

顾名思义,懒加载就是因为偷懒了,懒得加载了,只有使用的时候才进行加载。其实,懒加载也加延迟加载,主要以应用与Mybatis的关联查询,按照设置的延迟规则,推迟对延迟对关联对象的select查询,例如,我们在用Mybatis进行一对多的时候,先查询出一方,当程序需要多方数据时,mybatis会再次发出sql语句进行查询,减轻了对我们数据库的压力。Mybatis的延迟加载,只对关联对象有延迟设置。

如我们在之前查询的一个部门和旗下所有雇员,当程序没有访问雇员属性时,程序就不需要执行第二条sql,这就是懒加载。

我们开启懒加载,看看效果

  • 访问了雇员属性,可以看到执行了两条sql

    @Test
    public void get() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = mapper.get(1);
        System.out.println(dept);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 751021317.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cc3ad05]
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] ==>  Preparing: select * from dept where id=? 
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] ==> Parameters: 1(Integer)
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] <==      Total: 1
    [main] [aphler.mapper.EmployeeMapper.getByDid]-[DEBUG] ==>  Preparing: select * from employee where did=?; 
    [main] [aphler.mapper.EmployeeMapper.getByDid]-[DEBUG] ==> Parameters: 1(Integer)
    [main] [aphler.mapper.EmployeeMapper.getByDid]-[DEBUG] <==      Total: 3
    Dept(id=1, name=开发部, employeeList=[Employee(id=1, name=小明), Employee(id=3, name=小张), Employee(id=5, name=小王)])
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cc3ad05]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cc3ad05]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 751021317 to pool.
    
  • 没有访问雇员属性,可以看到只执行了一条sql

    @Test
    public void get() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
        Dept dept = mapper.get(1);
        System.out.println(dept.getId());
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 1348916831.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5066d65f]
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] ==>  Preparing: select * from dept where id=? 
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] ==> Parameters: 1(Integer)
    [main] [aphler.mapper.DeptMapper.get]-[DEBUG] <==      Total: 1
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5066d65f]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5066d65f]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 1348916831 to pool.
    
全局懒加载
<settings>
    <setting name="logImpl" value="LOG4J"/>
    <!--
        aggressiveLazyLoading:
            启用时: 有延迟加载属性的对象在被调用时将会完全加载任意属性。
            禁用时: 调用哪个懒载属性就加载哪个属性, 按需加载
     -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!--
        lazyLoadingEnabled:
            全局启用或禁用延迟加载。
			禁用时: 所有关联对象都会即时加载。
     -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
局部懒加载

associationcollection标签添加fetchType="lazy"

<resultMap id="employee" type="org.hong.pojo.Employee">
    <id property="id" column="id"></id>
    <result property="name" column="name"></result>
    <!--
        association:定义关联对象的封装规则
           select: 表明当前属性是调用select指定的方法查出的结果
           column: 指定将那一列的值传给select
           fetchType: 关联属性的加载策略, 可以覆盖全局的lazyLoadingEnabled, fetchType属性同样可以作用于collection标签
               lazy: 延迟加载
               eager: 即时加载
     -->
    <association property="dept"
                 column="did"
                 javaType="org.hong.pojo.Dept"
                 fetchType="lazy"
                 select="org.hong.mapper.DeptMapper.get"></association>
</resultMap>

总结

  • 嵌套查询的方式在查询时会向数据库发送多次SQL语句
  • 联合查询的方式只会向数据库发送一次SQL语句

  • resultMap:自定义某个javaBean的封装规则

  • resultMap的属性

    • id:唯一标识
    • type:自定义规则的java类型
  • resultMap包含的标签

    • result:指定其余键的封装规则
    • id:指定主键的封装规则, id定义主键底层会有优化
      • column:指定数据库的列
      • property:指定对应的javaBean属性

  • association:定义关联对象的封装规则

  • collection:定义关联集合类型的属性的封装规则

    • property:当前封装的对象的属性
    • javaType:指定实体类中属性的类型
    • ofType:指定映射到List或集合中的pojo类型,泛型中的约束类型
    • select:表明当前属性是调用select指定的方法查出的结果
    • column: 指定将哪一列的值传给select
    • // 传递多列的值
      column="{key1=column1, key2=column2}"
          key: select指定的查询的#{key}中的key
          colnmn: 列名
      
    • 流程:使用select指定的方法(传入column指定的列的参数值)查出对象, 并封装给property
    • fetchType:在全局配置中设置了延迟加载的情况下可以将联合属性修改为立即加载
      • lazy: 延迟, 默认
      • eager: 立即

动态sql

环境搭建

  • SQL

    DROP TABLE IF EXISTS `blog`;
    CREATE TABLE `blog` (
      `id` varchar(50) NOT NULL COMMENT '博客id',
      `title` varchar(100) NOT NULL COMMENT '博客标题',
      `author` varchar(30) NOT NULL COMMENT '博客作者',
      `create_time` datetime NOT NULL COMMENT '创建时间',
      `views` int(30) NOT NULL COMMENT '浏览量',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    INSERT INTO `blog` VALUES ('0001', 'hong/My-Note', '谢禹宏', '2021-04-21 00:00:00', '100');
    INSERT INTO `blog` VALUES ('0002', 'MyBatis-Plus', '苞米豆', '2021-04-21 00:00:00', '100');
    INSERT INTO `blog` VALUES ('0003', 'Hello MyBatis', '母鸡', '2021-04-21 00:00:00', '120');
    INSERT INTO `blog` VALUES ('0004', 'Hello Vuew', '尤雨溪', '2021-01-21 00:00:00', '100');
    INSERT INTO `blog` VALUES ('0005', 'Hello Linux', '林纳斯', '2001-04-21 00:00:00', '120');
    

    数据如下图

    image-20211021151158593

  • 创建BlogMapper接口和xml文件

if

查询博客, 携带了哪个字段查询条件就带上这个字段的值

  • 接口方法

    List<Blog> getByBlog(Blog blog);
    
  • 接口映射

    <select id="getByBlog" resultType="aphler.entity.Blog">
        select * from blog where
        <!-- test: 判断的表达式 (OGNL)-->
        <if test="title != null and title.trim() != ''">
            title like #{title}
        </if>
        <if test="author != null and author.trim() != ''">
            and author like #{author}
        </if>
    </select>
    

    这么写是有问题的,如当一个if没有满足时sql语句会多出一个and

    解决方案(后面where标签会详细说):

    1. 在 where 添加后面添加 1=1, 以后的条件都 and xxx``(不推荐, 会导致数据库性损失)
    2. MyBatis 使用 where 标签来将所有的查询条件包括在内,MyBatis 会自动的忽略where后第一个不合法的 andor, 并且在有条件的情况下自动拼接上 where
  • 测试用例

    @Test
    public void testIf() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setTitle("Hello%");
        List<Blog> blogList = mapper.getByBlog(blog);
        blogList.forEach(System.out::println);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
  • 执行结果

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 487416600.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1d0d6318]
    [main] [aphler.mapper.BlogMapper.getByBlog]-[DEBUG] ==>  Preparing: select * from blog where title like ? 
    [main] [aphler.mapper.BlogMapper.getByBlog]-[DEBUG] ==> Parameters: Hello%(String)
    [main] [aphler.mapper.BlogMapper.getByBlog]-[DEBUG] <==      Total: 3
    Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=null, views=120)
    Blog(id=0004, title=Hello Vuew, author=尤雨溪, createTime=null, views=100)
    Blog(id=0005, title=Hello Linux, author=林纳斯, createTime=null, views=120)
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1d0d6318]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1d0d6318]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 487416600 to pool.
    

    可以看出查询出了相应的结果

where

为了解决上节的问题,这里使用where标签来解决。where标签会为sql自动加上whereand

<select id="getByBlog" resultType="aphler.entity.Blog">
    select * from blog
    <where>
        <!-- test: 判断的表达式 (OGNL)-->
        <if test="title != null and title.trim() != ''">
            title like #{title}
        </if>
        <if test="author != null and author.trim() != ''">
            author like #{author}
        </if>
    </where>
</select>

问题

我们进行模糊查询时,每次给属性赋值都加上了 %%,显示的加上通配符,这样并不是很好,应该让 MyBatis 为我们加上通配符,想要完成这个功能需要使用 bind 元素。

bind

元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。通常用来拼接模糊查询,即自动给模糊查询的字段加上%%

Mapper 接口和测试不变,对 where 演示的方法映射进行改造!!!

<select id="getByBlog" resultType="aphler.entity.Blog">
    select * from blog
    <where>
        <!-- test: 判断的表达式 (OGNL)-->
        <if test="title != null and title.trim() != ''">
            <bind name="newTitle" value="'%'+title+'%'"></bind>
            title like #{newTitle}
        </if>
        <if test="author != null and author.trim() != ''">
            <bind name="newAuthor" value="'%'+author+'%'"/>
            author like #{newAuthor}
        </if>
    </where>
</select>

bind也可以是单表签

set

set元素会动态地在行首插入 SET关键字,并会删掉添加额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  • 接口方法

    int update(Blog blog);
    
  • 方法映射

    这里面的逗号可以删除,set会根据条件自行添加

    <update id="update">
        update blog
        <set>
            <if test="title != null and title.trim() != ''">
                , title = #{title}
            </if>
            <if test="author != null and author.trim() != ''">
                , author = #{author}
            </if>
            <if test="createTime != null">
                , create_time = #{createTime}
            </if>
            <if test="views != null and views > 0">
                , views = #{views}
            </if>
        </set>
        where id=#{id}
    </update>
    
  • 测试用例

    @Test
    public void testSet(){
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        blog.setId("0004");
        blog.setAuthor("s12");
        int update = mapper.update(blog);
        System.out.println(update);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    

choose{when, otherwise}

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。(choose --> switch,when --> case,otherwise --> default)

  • 接口方法

    // 有id, 根据id精准匹配; 有title就根据title进行模糊查询; 如果都没有就查询author为苞米豆的blog
    List<Blog> getByBlogChoose(Blog blog);
    
  • 接口映射

    <select id="getByBlogChoose" resultType="aphler.entity.Blog">
        select * from blog
        <where>
            <choose>
                <when test="id != null and id > 0">
                    and id = #{id}
                </when>
                <when test="title != null and title.trim() != ''">
                    <bind name="title" value="'%' + title + '%'"/>
                    and title like #{title}
                </when>
                <otherwise>
                    and author = '苞米豆'
                </otherwise>
            </choose>
        </where>
    </select>
    
  • 测试用例

    @Test
    public void testChoose() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Blog blog = new Blog();
        //blog.setId("0004");
        blog.setTitle("Hello");
        List<Blog> blogList = mapper.getByBlogChoose(blog);
        blogList.forEach(System.out::println);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
  • 运行结果

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 183155105.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@aeab9a1]
    [main] [aphler.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==>  Preparing: select * from blog WHERE title like ? 
    [main] [aphler.mapper.BlogMapper.getByBlogChoose]-[DEBUG] ==> Parameters: %Hello%(String)
    [main] [aphler.mapper.BlogMapper.getByBlogChoose]-[DEBUG] <==      Total: 3
    Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=null, views=120)
    Blog(id=0004, title=Hello Vuew, author=尤雨溪, createTime=null, views=100)
    Blog(id=0005, title=Hello Linux, author=林纳斯, createTime=null, views=120)
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@aeab9a1]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@aeab9a1]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 183155105 to pool.
    

foreach

  • 接口方法

    //根据id数组获取博客
    List<Blog> getByIds(List<String> ids);
    
  • 方法映射

    <select id="getByIds" resultType="aphler.entity.Blog">
        select * from blog
        <where>
            <if test="list!=null and list.size>0"></if>
            <!--
                               collection: 指定遍历的集合; 只能写与集合类型对应的名字,如果想使用其他名称则必须使用@param注解指定名称
                               item: 将当前遍历的元素赋值给指定的变量
                               separator: 元素之间的分隔符
                               open: 指定开始符号
                               close: 指定结束符号
                               index: 遍历List的时候是index索引, item是当前值
                                      遍历Map的时候index是map的key, item是map的值
                           -->
            <foreach collection="collection" item="id" open="id in(" separator="," close=")">
                #{id}
            </foreach>
        </where>
    </select>
    
  • 测试用例

    @Test
    public void testForeach() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        ArrayList<String> ids = new ArrayList<>();
        ids.add("0001");
        ids.add("0003");
        ids.add("0005");
        List<Blog> blogList = mapper.getByIds(ids);
        blogList.forEach(System.out::println);
        // 4.提交事务
        sqlSession.commit();
        // 5.关闭sqlSession
        sqlSession.close();
    }
    

Sql (抽取可重用SQL片段)

场景:在真实开发中我们不能写这样的 SQL 语句 select * from xxx,是不能写 * 的,但是每写一个查询语句都写上全部的列名,就造成了代码的冗余,而且也不易于维护。还好 MyBatis 提供了解决方案。如果表中字段发生了更改,我们只需要修改 sql 片段就 OK 了。

  • 接口方法

    List<Blog> getAll();
    
  • 方法映射

    <!--
        sql: 抽取片段
            id: 唯一标识
        -->
    <sql id="column">
        id, title, author, create_time, views
    </sql>
    <select id="getAll" resultType="aphler.entity.Blog">
        <!--
                include: 引入sql节点定义的sql片段
                    refid: 引用指定id的sql片段
             -->
        select
        <include refid="column"/>
        from blog
    </select>
    

缓存

简介

  1. 什么是缓存 [ Cache ] ?
    • 存在内存中的临时数据
    • 将用户经常查询的数据放在缓存(内存)中,用户查询数据就不用从数据库中查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  1. 为什么使用缓存
    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  1. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据

MyBatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两个缓存:一级缓存二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高拓展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存

一级缓存

  • 一级缓存也叫本地缓存

    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再取查询数据库

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太小了,所以诞生了二级缓存

  • 基于namespace级别的缓存,一个名称空间对应一个二级缓存

  • 工作机制

    • 一个会话查询一条数据,这个数据会被放到当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们开启了二级缓存,会话关闭了,一级缓存中的数据会被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放再自己对应的缓存中

缓存失效的情况

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

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

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

  • 同一个SqlSession两次查询期间手动清空了缓存

二级缓存的使用

  • 开启全局缓存

    <!-- 开启二级缓存总开关 --> 
    <setting name="cacheEnabled" value="true"/>
    
  • 开启某个mapper的二级缓存

    <!-- 
      开启mapper下的namespace的二级缓存,
      cache标签中的所有属性都是可选的
     --> 
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
  • cache标签属性

    eviction: 缓存的回收策略, 默认的是 LRU

    • LRU – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

    flushInterval: 缓存刷新间隔, 默认不清空,缓存多长时间清空一次, 设置一个毫秒值

    readOnly: 是否只读, 默认false

    • true: 只读, mybatis认为所有从缓存中获取数据的操作都是只读操作, 不会修改数据。mybatis为了加快获取速度, 直接就会将数据在缓存中的引用交给用户。不安全, 速度快
    • false: 非只读, mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全, 速度慢。

    size: 缓存存放多少元素;

    type: 指定自定义缓存的全类名;

缓存原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gLWr0ZIr-1637293521996)(https://i.loli.net/2021/10/21/vKijFy57dxoqcVm.png)]

MyBatis整合ehcache

  1. 导入以依赖

    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-web</artifactId>
        <version>2.0.4</version>
        <optional>true</optional>
    </dependency>
    
  2. mapper.xml 中使用自定义缓存

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

逆向工程 ( MBG )

MyBatis Generator 简称 MBG ,是一个专门为 MyBatis 框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及 bean 类。支持基本的增删改查,以及 QBC 风格的条件查询。但是表连接、存储过程等这些复杂 sql 的定义需要我们手工编写

  1. 导入依赖

    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    
  2. generationConfig.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, 支持QBC风格
         -->
        <context id="mybatisGenerator" targetRuntime="MyBatis3Simple">
            <property name="javaFileEncoding" value="UTF-8"/>
            <!--定义生成的java类的编码格式-->
            <commentGenerator>
                <!-- 是否去除自动生成的注释 true:是 : false:否 -->
                <property name="suppressAllComments" value="true"/>
            </commentGenerator>
            <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
            <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"
                            userId="root"
                            password="123456">
                <!-- MySQL 不支持 schema 或者 catalog 所以需要添加这个,官方文档有,否则会生成整个mysql中所有的表或所有同名的表 -->
                <property name="nullCatalogMeansCurrent" value="true"/>
            </jdbcConnection>
    
            <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
                NUMERIC 类型解析为java.math.BigDecimal -->
            <javaTypeResolver>
                <property name="forceBigDecimals" value="false"/>
            </javaTypeResolver>
    
            <!-- targetProject:生成POJO类的位置 -->
            <javaModelGenerator targetPackage="mbg.entity"
                                targetProject=".\src\main\java">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false"/>
                <!-- 从数据库返回的值被清理前后的空格 -->
                <property name="trimStrings" value="true"/>
            </javaModelGenerator>
            <!-- targetProject:mapper映射文件生成的位置 ,即xml文件的位置-->
            <sqlMapGenerator targetPackage="xml"
                             targetProject=".\src\main\resources">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false"/>
            </sqlMapGenerator>
            <!-- targetPackage:mapper接口生成的位置 -->
            <javaClientGenerator type="XMLMAPPER"
                                 targetPackage="mbg.mapper"
                                 targetProject=".\src\main\java">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false"/>
            </javaClientGenerator>
            <!-- 指定数据库表,所有的表用通配符 % -->
            <!--        <table tableName="blog" domainObjectName="Blog"></table>-->
            <table tableName="%"></table>
        </context>
    </generatorConfiguration>
    
  3. 使用

    // 运行这个单元测试, 自动生成
    @Test
    public void testMbg() throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("C:\\java_project\\mybatis_study\\src\\main\\resources\\generationConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
    

pageHelper 分页查询

Mybatis没有提供分页查询,需要我们自己limit,也可以使用pageHelper插件

环境搭建

同动态sql环境一样,执行那个sql

依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

使用

  1. 在mybatis核心配置文件中配置拦截器插件

    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
  2. 使用

    @Test
    public void testPage() {
        // 1.获取sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 2.获取需要的mapper接口的代理对象
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Page<Object> page = PageHelper.startPage(2, 2);
        List<Blog> blogList = mapper.getByPage();
        blogList.forEach(System.out::println);
    
        //page中封装的对象
        System.out.println("当前页码:" + page.getPageNum());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("每页的记录数:" + page.getPageSize());
        System.out.println("总页码:" + page.getPages());
        // 4.提交事务
        sqlSession.commit();
    
        // 5.关闭sqlSession
        sqlSession.close();
    }
    
  3. 运行结果

    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.logging.LogFactory]-[DEBUG] Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] PooledDataSource forcefully closed/removed all connections.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Created connection 2001115307.
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 2001115307 to pool.
    [main] [SQL_CACHE]-[DEBUG] Cache Hit Ratio [SQL_CACHE]: 0.0
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Opening JDBC Connection
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Checked out connection 2001115307 from pool.
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@774698ab]
    [main] [aphler.mapper.BlogMapper.getByPage_COUNT]-[DEBUG] ==>  Preparing: SELECT count(0) FROM blog 
    [main] [aphler.mapper.BlogMapper.getByPage_COUNT]-[DEBUG] ==> Parameters: 
    [main] [aphler.mapper.BlogMapper.getByPage_COUNT]-[DEBUG] <==      Total: 1
    [main] [aphler.mapper.BlogMapper.getByPage]-[DEBUG] ==>  Preparing: select * from blog LIMIT ?, ? 
    [main] [aphler.mapper.BlogMapper.getByPage]-[DEBUG] ==> Parameters: 2(Long), 2(Integer)
    [main] [aphler.mapper.BlogMapper.getByPage]-[DEBUG] <==      Total: 2
    Blog(id=0003, title=Hello MyBatis, author=母鸡, createTime=null, views=120)
    Blog(id=0004, title=Hello Vuew, author=尤雨溪, createTime=null, views=100)
    当前页码:2
    总记录数:5
    每页的记录数:2
    总页码:3
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@774698ab]
    [main] [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-[DEBUG] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@774698ab]
    [main] [org.apache.ibatis.datasource.pooled.PooledDataSource]-[DEBUG] Returned connection 2001115307 to pool.
    

常见用法

  • PageHelper.startPage(pageNum, pageSize);页码,每页条数

  • PageHelper.offsetPage(offset, limit);起始索引,查询条数

  • 配置 PageHelper 插件时配置 **supportMethodsArguments** 属性为 **true**

    <plugins>
       <!-- com.github.pagehelper为PageHelper类所在包名 -->
       <plugin interceptor="com.github.pagehelper.PageInterceptor">
           <property name="supportMethodsArguments" value="true"/>
       </plugin>
    </plugins>
    

    Mapper:

    //分页查询
    // sql中不需要处理pageNum和pageSize
    //pageNum 起始页数, 必须使用@Param注解指定key为pageNum
    //pageSize  每页数量, 必须使用@Param注解指定key为pageSize
    @Select("select * from blog")
    List<Blog> getByPageMethod(@Param("pageNum") Integer pageNum, @Param("pageSize") Integer pageSize);
    

    使用

    @Test
    public void testPageMethod() {
       // 1.获取sqlSession对象
       SqlSession sqlSession = MybatisUtils.getSqlSession();
       // 2.获取需要的mapper接口的代理对象
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       List<Blog> blogList = mapper.getByPageMethod(1, 3);
       blogList.forEach(System.out::println);
       // 4.提交事务
       sqlSession.commit();
    
       // 5.关闭sqlSession
       sqlSession.close();
    }
    

常用的属性

  • pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
  • reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
  • supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。

批量操作

使用分页查询的环境进行演示

  1. mapper

    //演示批量操作
    @Insert("insert into blog values(#{id}, #{title}, #{author}, #{createTime}, #{views})")
    void save(Blog blog);
    
  2. 使用

    批量插入,每500条提交一次

    @Test
    public void testBatch() {
        SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSessionFactory();
        // 调用openSession时传入ExecutorType枚举类的对象, 指明执行类型
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            long start = System.currentTimeMillis();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            for (int i = 1; i <= 100000; i++) {
                Blog blog = new Blog();
                blog.setId("00" + i);
                blog.setTitle("测试Batch" + "-" + i);
                blog.setAuthor("谢禹宏");
                blog.setCreateTime(Date.valueOf(LocalDate.now()));
                blog.setViews(i);
                mapper.save(blog);
                if (i % 500 == 0) {
                    //手动每500条提交一次, 提交后无法回滚
                    sqlSession.commit();
                    //清理缓存, 防止溢出
                    sqlSession.clearCache();
                }
            }
            sqlSession.commit();
            long end = System.currentTimeMillis();
            System.out.println(end - start);
        } catch (Exception e) {
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }
    }
    

整合SpringBoot

  1. 新建SpringBoot模块spring_mybatis

  2. 导入mybatis依赖

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    
  3. 编写xml,mapper等文件

  4. 编写配置文件

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
    mybatis:
      mapper-locations: mapper/*.xml
    
  5. controller

    @RestController
    public class Controller {
    
        @Autowired
        private BlogMapper blogMapper;
    
        @GetMapping("blog")
        public String test(String id) {
            return blogMapper.getById(id).toString();
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值