Java框架-01-MyBatis

1. MyBatis简介

  1. MyBatis提供了数据库查询的自动对象绑定功能;SQL写在XML中,便于统一管理和优化,解除了SQL与代码的耦合;执行效率比Hibernate快;对于复杂SQL,使用MyBatis会更加灵活。但关联的表或字段较多时,SQL工作量很大;而且SQL依赖于数据库,导致数据库移植性较差。
  2. Hibernate是全自动,Hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成SQL;对于单表的CRUD,使用Hibernate开发更加高效。但学习和精通的门槛较高,而且由于是自动生成SQL,因此无法直接维护SQL。
  3. MyBatis官网:https://mybatis.org/mybatis-3/
  4. MyBatis作用:
    1. 减少JDBC的重复代码
    2. 使数据库环境信息的硬编码和执行的SQL语句的硬编码解耦
  5. MyBatis分页插件PageHelper网址:https://pagehelper.github.io/

2. MyBatis基本使用

以查询user表中所有user为例

2.1 创建Maven工程,添加坐标

<dependencies>
    <!--添加坐标-->
    <!--junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--lombok的依赖-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--mybatis的依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.3</version>
    </dependency>
</dependencies>

2.2 创建POJO

-- 创建user表
CREATE TABLE user(
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
    username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用户名",
    password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密码",
    balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余额"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用户表";

-- 添加用户
INSERT INTO user VALUES
    (NULL, 'user1', 'password1', 1000),
    (NULL, 'user2', 'password2', 2000),
    (NULL, 'user3', 'password3', 3000);


@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String username;
    private String password;
    private Double balance;
}

2.3 创建PojoDao接口

原则:MyBatis的所有PojoDao接口,方法参数只设置一个;

由于是全类名+方法名的保存和寻找策略,因此PojoDao接口里的方法不能重载

PojoDao接口的工作原理是JDK动态代理

public interface UserDao {
    void addUser(User user);

    void deleteById(Integer id);

    void updateUser(User user);

    List<User> findAll();
}

2.4 创建外部jdbc配置文件

在resources下创建jdbc.properties文件:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db01?useSSL=false
jdbc.username=root
jdbc.password=123456

2.5 创建MyBatis核心配置文件*

  1. 存储在resources根路径下
  2. 文件名随意(如:SqlMapConfig.xml)
  3. 需要引入约束

核心配置文件的顺序:

  1. properties:引入外部properties文件

  2. settings:全局配置参数

  3. typeAliases:给类型设置别名

    核心配置文件:typeAliases定义单个别名
    <typeAliases>
        <typeAlias type="com.liu2m.pojo.User" alias="User"></typeAlias>
    </typeAliases>
    核心配置文件:typeAliases批量定义别名
    <typeAliases>
        <package name="com.liu2m.pojo"/>
    </typeAliases>
    
    修改PojoDao.xml:
    <select id="findAll" resultType="User">
        select * from user
    </select>
    
  4. typeHandlers:类型处理器

  5. objectFactory:对象工厂

  6. objectWrapperFactory

  7. reflectorFactory

  8. plugins:插件

  9. environments:环境配置

  10. databaseIdProvider:数据库厂商标识

  11. mappers:映射器

<?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>
    <!--引入外部properties文件-->
    <properties resource="jdbc.properties"></properties>
    <!--给类型设置别名-->
    <typeAliases>
        <package name="com.liu2m.pojo"/>
    </typeAliases>
    <!--配置连接数据库的环境
    default:指定使用哪一个环境-->
    <environments default="dev">
        <!--一个连接数据库的环境
        id:环境变量名-->
        <environment id="dev">
            <!--配置事务管理者
            MyBatis事务使用的是jdbc-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置连接池
            UNPOOLED:不使用连接池
            POOLED:使用mybatis内置的连接池-->
            <dataSource type="POOLED">
                <!--value值可以直接写,也可以通过${}设置
                ${}中写properties引入的文件中的属性名-->
                <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>

        <environment id="test">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db01?useSSL=false&amp;characterEncoding=UTF8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--加载所有的映射文件-->
    <mappers>
        <!--引入一个映射文件-->
        <!--方式1:通过resource属性引入映射文件的路径-->
        <!--<mapper resource="com/liu2m/dao/UserDao.xml"/>-->

        <!--方式2:扫描接口-->
        <!--配置单个接口-->
        <!--<mapper class="com.liu2m.dao.UserDao"></mapper>-->
        <!--批量配置-->
        <package name="com.liu2m.dao"/>
    </mappers>
</configuration>

2.6 创建SQL映射文件*

  1. 存储在resources里面
  2. 全路径和文件名要和相应的PojoDao接口的相同(在resources里创建多级文件夹时用/分隔,而不是.)
  3. 需要引入约束

根标签mapper的namespace属性的值为对应的PojoDao接口的全限定名

根标签mapper下的每一个子标签对应PojoDao接口中的一个方法:

  1. 查询方法对应select标签
  2. 添加方法对应insert标签
  3. 删除方法对应delete标签
  4. 修改方法对应update标签

子标签的id对应方法的名字

子标签的parameterType对应方法的形参类型

子标签的resultType(只有select标签有)对应方法的返回值类型:

  1. 如果返回值类型是简单类型,则直接写对应的Java类型
  2. 如果返回值类型是POJO、Map,则直接写POJO的全限定名
  3. 如果返回值类型是Collection,则resultType是集合中元素的类型
  • 结果集的字段名和POJO的属性名能够对应时,resultType才可以自动映射

使用参数中的数据:

  1. 如果参数为pojo、map,则通过#{属性名或Map的key}来引入对应的值
  2. 如果参数为基本类型数据,则通过#{任意字符串}来引入对应的值
  3. 如果参数为pojo包含pojo,如ResultBean中包含User,则使用#{user.username}

resultMap标签:解决查询出来的结果的列名和JavaBean属性不一致的情况

  1. id属性:映射规则的唯一id
  2. type属性:将SQL查询出来的结果手动映射到哪个类
<?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.liu2m.dao.UserDao">
    <resultMap id="findAllMap" type="User">
        <!--id:查询结果集的唯一标识;如果唯一约束是多个字段段,则定义多个id-->
        <id property="id" column="u_id"></id>
        <!--
        property:指定的POJO类的属性
        column:sql查询出来的字段名-->
        <result property="username" column="u_username"></result>
        <result property="password" column="u_password"></result>
        <result property="balance" column="u_balance"></result>
    </resultMap>
    <select id="findAll" resultMap="findAllMap">
        select id as u_id, username as u_username, password as u_password, balance as u_balance from user
    </select>

    <!--selectKey可以获取主键,存储在对应的POJO对象中
    keyColumn:要查询的字段名
    keyProperty:将查询到的结果赋值到哪个属性
    resultType:查询到的结果的类型
    order:在执行添加之前还是之后执行selectKey里的SQL-->
    <insert id="addUser" parameterType="com.liu2m.pojo.User">
        <selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user values (null, #{username}, #{password}, #{balance})
    </insert>

    <update id="updateUser" parameterType="com.liu2m.pojo.User">
        update user set username = #{username}, password = #{password}, balance = #{balance}
    </update>

    <delete id="deleteById" parameterType="java.lang.Integer">
        delete from user where id = #{id}
    </delete>
</mapper>

2.7 单元测试

public class UserDaoTest {
    InputStream is;
    SqlSession sqlSession;
    UserDao userDao;

    @Before
    public void init() throws IOException {
        // 1. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2. 读取核心配置文件,转换成字节输入流
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 3. 创建SqlSessionFactory对象
        SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(is);
        // 4. 创建SqlSession对象
        sqlSession = sessionFactory.openSession();
        // 5. 创建UserDao的代理对象
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @Test
    public void testFindAll() {
        // 6. 调用需要执行的方法
        List<User> userList = userDao.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
    }

    @Test
    public void testAddUser() {
        User user = new User(0, "user4", "654321", 4000.0);
        userDao.addUser(user);
        System.out.println(user.getId());
    }

    @After
    public void destroy() throws IOException {
        // 在CUD操作中,需要通过sqlSession.commit()方法来执行提交操作
        sqlSession.commit();
        // 7. 释放资源
        sqlSession.close();
        is.close();
    }
}

2.8 #{}与${}的区别*

  1. 传参的区别:
    1. #{}先用?占位,然后再设置参数,可以防止SQL注入
    2. ${}直接拼接SQL语句
  2. #{}不能写在引号里,而${}必须要写在引号里
  3. pojo、map类型的参数,#{}和${}括号里都是写属性名或Map的key
  4. 基本类型、字符串类型的参数,#{}和${}括号里可以写任意字符串
  • 模糊查询:
    1. "%"#{username}"%"
    2. concat("%", #{username}, "%")
    3. "%${value}%"

2.9 自定义SqlSessionFactory工具类

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class SqlSessionFactoryUtils {
    private static InputStream is;
    private static SqlSessionFactory sessionFactory;

    static {
        try {
            // 1. 创建SqlSessionFactoryBuilder对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2. 读取核心配置文件,转换成字节输入流
            is = Resources.getResourceAsStream("SqlMapConfig.xml");
            // 3. 创建SqlSessionFactory对象
            sessionFactory = sqlSessionFactoryBuilder.build(is);
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取一个SqlSession
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = null;
        // 4. 创建SqlSession对象
        sqlSession = sessionFactory.openSession();
        return sqlSession;
    }

    /**
     * 提交事务并关闭资源
     */
    public static void commitAndClose(SqlSession sqlSession) {
        sqlSession.commit();
        sqlSession.close();
    }

    /**
     * 回滚事务并关闭资源
     */
    public static void rollbackAndClose(SqlSession sqlSession) {
        sqlSession.rollback();
        sqlSession.close();
    }
}

3. MyBatis日志

  1. 导入坐标:

    <!--使用log4j日志打印框架打印mybatis日志-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.6</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.6</version>
    </dependency>
    
  2. 在resources下创建log4j.properties文件:

    #日志级别:ERROR > WARN > INFO > DEBUG
    log4j.rootLogger=DEBUG,stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=[%-5p] %t %l %d %rms:%m%n
    log4j.appender.file=org.apache.log4j.FileAppender
    log4j.appender.file.File=E:\\Logs\\IdeaLogs\\IdeaLogs.log
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
    

4. Mybatis动态SQL*

动态SQL:SQL语句的主体结构,在编译时无法确定,只有等到程序运行后,在执行过程中才能确定

4.1 if标签

满足test属性的逻辑表达式,则if标签里面的语句有效

多条件查询时,每次查询的条件不确定有哪些,就可以使用if标签

当参数值为int的0时,MyBatis会将参数值其转化为空字符串

select * from user
<!--如果参数balance大于0,则添加balance的条件-->
<if test="balance != null and balance > 0">
    where balance = #{balance}
</if>

4.2 where标签

select * from user
<!--where标签的作用:
如果有if条件成立,则会在SQL语句中添加WHERE关键字,并且还会自动去掉第一个条件的and-->
<where>
    <if test="balance != null and balance > 0">
        and balance = #{balance}
    </if>
    <if test="password != null and password.length > 6">
        and password = #{password}
    </if>
</where>

4.3 foreach标签

<!--删除所有在id集合中的行-->
delete from user
<!--
collection:要遍历的对象;如果要遍历的对象是一个集合,就写list
item:遍历出来的每一个元素
index:遍历出来的每一个元素的下标
separator:分隔符
open:在遍历出来的第一个元素之前拼接的字符串
close:在遍历出来的最后一个元素之后拼接的字符串
-->
<foreach open="where id in (" close=")" collection="list" item="id" index="i" separator=", ">
    #{id}
</foreach>

4.4 sql标签和include标签

sql标签可以定义公共的sql片段,需要时可以用include标签引入sql片段

<sql id="findAll">
    select * from user
</sql>
<select id="findAll" resultType="User" parameterType="int">
    <include refid="findAll"></include>
    where id = #{id}
</select>

5. Mybatis多表关联查询*

建立数据库环境:

DROP TABLE IF EXISTS user;
-- 创建用户表
CREATE TABLE user(
    uid INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
    username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用户名",
    password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密码",
    balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余额"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用户表";
-- 添加用户
INSERT INTO user VALUES
    (NULL, 'user1', 'password1', 1000),
    (NULL, 'user2', 'password2', 2000),
    (NULL, 'user3', 'password3', 3000);

-- 创建账户表
CREATE TABLE account (
    aid INT AUTO_INCREMENT PRIMARY KEY,
    money DOUBLE,
    uid INT,
    CONSTRAINT fk_account_uid FOREIGN KEY (uid) REFERENCES user(uid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加账户
INSERT INTO account VALUES
    (NULL, 1000, 1),
    (NULL, 2000, 1),
    (NULL, 1000, 2),
    (NULL, 2000, 2),
    (NULL, 3000, 3);

-- 创建课程表
CREATE TABLE course(
    cid INT PRIMARY KEY AUTO_INCREMENT,
    coursename VARCHAR(255),
    book VARCHAR(255)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加课程
INSERT INTO course VALUES
    (NULL, '面向对象程序设计', 'Java编程思想'),
    (NULL, '数据结构与算法', '算法导论'),
    (NULL, '设计模式', '大话设计模式');

-- 创建选课表
CREATE TABLE course_selection(
    uid INT,
    cid INT,
    CONSTRAINT fk_course_selection_uid FOREIGN KEY (uid) REFERENCES user(uid),
    CONSTRAINT fk_course_selection_cid FOREIGN KEY (cid) REFERENCES course(cid),
    PRIMARY KEY (uid, cid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加选课信息
INSERT INTO course_selection VALUES
    (1, 1),
    (1, 2),
    (1, 3),
    (2, 1),
    (2, 2),
    (3, 1);

5.1 一对一

根据账户id查询账户信息,并关联查询对应的用户表信息;由于一个账户只能对应一个用户,因此为一对一查询

在外键所在的POJO中新增一个外键参考的POJO类型的属性

  1. 创建Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一个Account对应一个User
         */
        private User user;
    }
    
  2. 创建PojoDao接口:

    public interface AccountDao {
        Account findAccountUserByAid(int aid);
    }
    
  3. 创建SQL映射文件:

    <?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.liu2m.dao.AccountDao">
        <!--使用resultMap自定义映射规则-->
        <resultMap id="accountUserMap" type="Account">
            <id column="aid" property="aid"></id>
            <result column="money" property="money"></result>
            <result column="uid" property="uid"></result>
            <!--使用association标签配置一对一的映射
            property:要映射的POJO属性的变量名
            javaType:要映射的POJO属性的类型-->
            <association property="user" javaType="User">
                <!--result语法同上面的result-->
                <result column="uid" property="uid"></result>
                <result column="username" property="username"></result>
                <result column="password" property="password"></result>
                <result column="balance" property="balance"></result>
            </association>
        </resultMap>
        <select id="findAccountUserByAid" parameterType="int" resultMap="accountUserMap">
            SELECT * FROM user JOIN account ON user.uid = account.uid WHERE account.aid = #{aid}
        </select>
    </mapper>
    
  4. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountUserByAid(1);
        System.out.println(account);
        /**
         * 结果:
         * Account(aid=1, money=1000.0, uid=1, user=User(id=null, uid=1, username=user1, password=password1, balance=1000.0))
         */
    }
    

5.2 一对多

根据用户id查询用户信息,并关联查询对应的所有账户表信息;由于一个用户可能对应多个账户,因此为一对多查询

在外键参考的POJO中新增一个List<外键所在POJO>类型的属性

  1. 创建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一个User对应多个Account
         */
        private List<Account> accountList;
    }
    
  2. 创建PojoDao接口:

    public interface UserDao {
        User findUserAccountByUid(int uid);
    }
    
  3. 创建SQL映射文件:

    <?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.liu2m.dao.UserDao">
        <!--使用resultMap自定义映射规则-->
        <resultMap id="userAccountMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <!--使用collection标签配置一对多的映射
            property:要映射的POJO属性的变量名
            ofType:查询出的每一行数据映射成哪一个POJO类型-->
            <collection property="accountList" ofType="Account">
                <!--result语法同上面的result-->
                <result column="aid" property="aid"></result>
                <result column="money" property="money"></result>
                <result column="uid" property="uid"></result>
            </collection>
        </resultMap>
        <select id="findUserAccountByUid" parameterType="int" resultMap="userAccountMap">
            SELECT * FROM user JOIN account ON user.uid = account.uid WHERE user.uid = #{uid}
        </select>
    </mapper>
    
  4. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserAccountByUid(1);
        System.out.println(user);
        /**
         * 结果:
         * User(uid=1, username=user1, password=password1, balance=1000.0, 
         * accountList=[Account(aid=1, money=1000.0, uid=1, user=null), Account(aid=2, money=2000.0, uid=1, user=null)])
         */
    }
    

5.3 多对多

根据用户id查询用户所选的所有课程信息,相当于一个用户对应多个课程

多对多关系可以看成是双向的一对多关系

  1. 创建Course表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Course implements Serializable {
        private Integer cid;
        private String coursename;
        private String book;
    }
    
  2. 创建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一个User对应多个Course
         */
        private List<Course> courseList;
    }
    
  3. 创建PojoDao接口:

    public interface UserDao {
        User findUserCourseByUid(int uid);
    }
    
  4. 创建SQL映射文件:

    <?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.liu2m.dao.UserDao">
        <!--使用resultMap自定义映射规则-->
        <resultMap id="userCourseMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <collection property="courseList" ofType="Course">
                <result column="cid" property="cid"></result>
                <result column="coursename" property="coursename"></result>
                <result column="book" property="book"></result>
            </collection>
        </resultMap>
        <select id="findUserCourseByUid" parameterType="int" resultMap="userCourseMap">
            SELECT * FROM user JOIN course_selection ON user.uid = course_selection.uid JOIN course ON course.cid = course_selection.cid WHERE user.uid = #{uid};
        </select>
    </mapper>
    
  5. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserCourseByUid(2);
        System.out.println(user);
        /**
         * 结果:
         * User(uid=2, username=user2, password=password2, balance=2000.0,
         * courseList=[Course(cid=1, coursename=面向对象程序设计, book=Java编程思想), Course(cid=2, coursename=数据结构与算法, book=算法导论)])
         */
    }
    

6. Mybatis分步查询延迟加载*

  1. 立即加载:一调用方法就立即发起查询
  2. 延迟加载(懒加载):需要使用数据时,才发起查询;如果暂时不需要使用数据,则暂时不查询
    1. 缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
    2. 优点:先从单表查询,需要时再从关联表去关联查询;由于查询单表要比关联查询多张表速度要快,因此可以提高数据库性能
  3. 延迟加载的SQL语句类似于嵌套子查询,分步骤查询
  • 全局延迟加载:在MyBatis核心配置文件中配置

    <settings>
        <!--lazyLoadingEnabled=true:所有关联对象都会延迟加载
            特定关联关系中可设置fetchType属性来覆盖lazyLoadingEnabled-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--aggressiveLazyLoading=false:每个属性会按需加载
            true:对具有懒加载特性对象的任意调用都会加载该对象的所有属性-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    

6.1 association标签实现分步查询延迟加载

根据账户id查询账户信息,在需要用户信息时再关联查询对应的用户表信息

  1. 创建User表和Account表的POJO:

    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一个Account对应一个User
         */
        private User user;
    }
    
  2. 创建PojoDao接口:

    public interface AccountDao {
        Account findAccountByAid(int aid);
    }
    
    public interface UserDao {
        User findUserByUid(int uid);
    }
    
  3. 创建SQL映射文件AccountDao.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.liu2m.dao.AccountDao">
        <resultMap id="accountUserMap" type="Account">
            <id column="aid" property="aid"></id>
            <result column="money" property="money"></result>
            <result column="uid" property="uid"></result>
            <!--使用association标签配置一对一映射的延迟加载
            fetchType="lazy":局部延迟加载(配置了这个resultMap的查询会延迟加载)
            select:调用其他的select标签(方法)
            column:往调用的select标签(方法)中传入的参数-->
            <association property="user" javaType="User" fetchType="lazy"
                         select="com.liu2m.dao.UserDao.findUserByUid" column="uid">
            </association>
        </resultMap>
        <select id="findAccountByAid" parameterType="int" resultMap="accountUserMap">
            SELECT * FROM account WHERE aid = #{aid}
        </select>
    </mapper>
    
  4. 创建SQL映射文件UserDao.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.liu2m.dao.UserDao">
        <select id="findUserByUid" parameterType="int" resultType="User">
            SELECT * FROM user WHERE uid = #{uid}
        </select>
    </mapper>
    
  5. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountByAid(1);
        System.out.println(account.getMoney());
        System.out.println(account.getUser());
    }
    

6.2 collection标签实现分步查询延迟加载

根据用户id查询用户信息,在需要账户信息时再关联查询对应的所有账户表信息

  1. 创建User表和Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一个User对应多个Account
         */
        private List<Account> accountList;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
    }
    
  2. 创建PojoDao接口:

    public interface UserDao {
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        List<Account> findAccountListByUid(int uid);
    }
    
  3. 创建SQL映射文件UserDao.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.liu2m.dao.UserDao">
        <resultMap id="userAccountMap" type="User">
            <id column="uid" property="uid"></id>
            <result column="username" property="username"></result>
            <result column="password" property="password"></result>
            <result column="balance" property="balance"></result>
            <!--使用collection标签配置一对多映射的延迟加载
            fetchType="lazy":局部延迟加载(配置了这个resultMap的查询会延迟加载)
            select:调用其他的select标签(方法)
            column:往调用的select标签(方法)中传入的参数-->
            <collection property="accountList" ofType="Account" fetchType="lazy"
                        select="com.liu2m.dao.AccountDao.findAccountListByUid" column="uid">
            </collection>
        </resultMap>
        <select id="findUserByUid" parameterType="int" resultMap="userAccountMap">
            SELECT * FROM user WHERE uid = #{uid}
        </select>
    </mapper>
    
  4. 创建SQL映射文件AccountDao.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.liu2m.dao.AccountDao">
        <select id="findAccountListByUid" resultType="com.liu2m.pojo.Account">
            SELECT * FROM account WHERE uid = #{uid}
        </select>
    </mapper>
    
  5. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserByUid(1);
        System.out.println(user.getUsername());
        System.out.println(user.getAccountList());
    }
    

7. MyBatis缓存-

MyBatis缓存类别:

  1. 一级缓存:是sqlSession对象的缓存,自带的(不需要配置)不可卸载的(不想使用还不行);一级缓存的生命周期与sqlSession一致
  2. 二级缓存:是SqlSessionFactory的缓存,只要是同一个SqlSessionFactory创建的SqlSession就共享二级缓存的内容,并且可以操作二级缓存。二级缓存如果要使用,需要手动开启(需要配置)

7.1 一级缓存

  1. 查询时,先去缓存中找;如果缓存中没有,则从数据库查询用户信息,然后将查询到的信息存储到缓存中;如果缓存中有,则直接从缓存中获取信息。
  2. 清除一级缓存:
    1. sqlSession.commit(); // commit时清除一级缓存是为了避免数据发生增删改后产生脏读
    2. sqlSession.close();
    3. sqlSession.clearCache();
    4. 执行增删改操作

7.2 二级缓存

  1. 步骤1:在MyBatis核心配置文件中开启二级缓存(由于默认开启,因此这一步可省略)

    <!--cacheEnabled:
    true:开启二级缓存(默认)
    false:不开启二级缓存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 步骤2:在要使用二级缓存的SQL映射文件的mapper标签里增加cache标签

    <mapper namespace="com.liu2m.dao.UserDao">
        <cache></cache>
        <select id="findAll" resultType="com.liu2m.pojo.User">
            select * from user
        </select>
    </mapper>
    
  3. 步骤3:要使用二级缓存的POJO类必须实现Serializable接口

  • 只有当前的sqlSession.close()时,该sqlSession的数据才会存入二级缓存中
  • 执行增删改操作时会清除二级缓存
  • select标签属性:
    • flushCache默认为false
    • useCache默认为true
  • insert、delete、update标签属性:
    • flushCache默认为true
    • 没有useCache属性

8. MyBatis注解-

使用注解代替了SQL映射文件

@SelectKey():代替了selectKey标签
@Insert("SQL"):代替了insert标签
@Delete("SQL"):代替了delete标签
@Update("SQL"):代替了update标签
@Select("SQL"):代替了select标签

@Results(id = "", value = @Result())
或@Results(@Result())
或@Results({@Result(), @Result()})
@Result(column = "列名", property = "属性名", one = @One(select = "指定用来多表查询的sqlmapper"), many = @Many(select = ""))
    @Results代替了resultMap标签
    @Result代替了result标签和id标签
    @One()代替了association标签
    @Many()代替了collection标签

8.1 MyBatis注解实现基本CRUD

  1. 创建PojoDao接口:

    public interface UserDao {
        @SelectKey(keyProperty = "uid", keyColumn = "uid", before = false, statement = "select last_insert_id()", resultType = int.class)
        @Insert("INSERT INTO user VALUES (NULL, #{username}, #{password}, #{balance})")
        void addUser(User user);
    
        @Delete("delete from user where uid = #{id}")
        void deleteById(Integer id);
    
        @Update("update user set username = #{username}, password = #{password}, balance = #{balance} where uid = #{uid}")
        void updateUser(User user);
    
        @Select("select * from user")
        List<User> findAll();
    }
    
  2. 创建MyBatis核心配置文件

  3. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User(2, "user4", "password4", 4000.0);
        userDao.addUser(user);
        System.out.println(user.getUid());
        userDao.deleteById(3);
        userDao.updateUser(user);
        userDao.findAll().stream().forEach(System.out::println);
        SqlSessionFactoryUtils.commitAndClose(sqlSession);
    }
    

8.2 MyBatis注解实现一对一映射及延迟加载

  1. 创建Account表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account implements Serializable {
        private Integer aid;
        private Double money;
        private Integer uid;
        /**
         * 表示一个Account对应一个User
         */
        private User user;
    }
    
  2. 创建PojoDao接口:

    public interface UserDao {
        @Select("select * from user where uid = #{id}")
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        @Results(id = "accountUserMap", value = {
                @Result(column = "uid", property = "uid"),
                @Result(property = "user", one = @One(select = "com.liu2m.dao.UserDao.findUserByUid", fetchType = FetchType.LAZY), column = "uid")
        })
        @Select("select * from account where aid = #{aid}")
        Account findAccountByAid(int aid);
    }
    
  3. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
        Account account = accountDao.findAccountByAid(1);
        System.out.println(account.getMoney());
    }
    

8.3 MyBatis注解实现一对多映射及延迟加载

  1. 创建User表的POJO:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User implements Serializable {
        private Integer uid;
        private String username;
        private String password;
        private Double balance;
        /**
         * 表示一个User对应多个Account
         */
        private List<Account> accountList;
    }
    
  2. 创建PojoDao接口:

    public interface UserDao {
        @Results(id = "userAccountMap", value = {
                @Result(column = "uid", property = "uid", id = true),
                @Result(property = "accountList", many = @Many(select = "com.liu2m.dao.AccountDao.findAccountListByUid", fetchType = FetchType.LAZY), column = "uid")
        })
        @Select("SELECT * FROM user WHERE uid = #{uid}")
        User findUserByUid(int uid);
    }
    
    public interface AccountDao {
        @Select("SELECT * FROM account WHERE uid = #{uid}")
        List<Account> findAccountListByUid(int uid);
    }
    
  3. 单元测试:

    @Test
    public void test01() {
        SqlSession sqlSession = SqlSessionFactoryUtils.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = userDao.findUserByUid(1);
        System.out.println(user);
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • MyBatis 1. 概述: MyBatis是一个基于Java持久层的框架,内部封装了JDBC,简化了开发时对数据...
    青丶空阅读 205评论 0 0
  • 目录1 JDBC2 Mybaits概述3 Mybatis扩展4 Mybatis的加载和缓存5 Mybatis注解开...
    小小千千阅读 464评论 0 2
  • 延迟加载策略 1、背景 问题:在一对多的关系中,当我们有一个用户,它有100个账户。 在查询用户的时候,要不...
    魔笛使者阅读 392评论 0 0
  • ### 入门案例 第一个:创建maven工程并导入坐标 第二个:创建实体类和dao接口 第三步:创建Mybatis...
    Sylvester_f7ee阅读 196评论 0 0
  • 基于代理 Dao 实现 CRUD 操作 1、查询所有 mapper配置文件: select * from us...
    魔笛使者阅读 125评论 0 0