MyBatis详解

MyBatis入门

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

上面是官方文档的原话,具体怎么理解,咱们往下看

使用前准备

添加依赖

MyBatis是运行到Dao层的,我这里使用的是MySQL数据库,所以还需要有MySQL的依赖

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
</dependencies>

添加配置文件

在Maven中的resources目录添加配置文件,建议取名为mybatis-config.xml,由于不同的xml文件需要有相应的核心配置内容(下图红框),这个内容约束了xml文件应该如和编写,如何配置请参考官方教程,不了解Maven的可以看我之前写的Maven详解

image-20210405115047408

添加完如果有以下这种报错,提示URI is not registered

image-20210405115657136

我们可以在File->Settings->Languages&Framewordks添加内容

image-20210405115836214

更好的方法时点击小灯泡中的fech external resource

image-20210405120148863

这样自动就为我们添加进来了,这样做相当于手动添加了dtd文件,让idea能找到url,而上一种方式只是忽略了这个报错,之后编写标签并不会有代码提示哦

image-20210405120243889

<?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>
    <!-- 实际环境,下面部署了两种环境,当前项目需要开发时,default中就显示development,挑选自己的id就可以,这个是随意设置的 -->
    <environments default="development">
        <!-- 开发环境(调试阶段) -->
        <environment id="development">
            <!--采用JDBC的事务管理方法-->
            <transactionManager type="JDBC"/>
            <!--POOLED代表采取连接池的方式管理连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/develop"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>

        <!-- 生产环境(发布阶段) -->
        <environment id="production">
            <!--采用JDBC的事务管理方法-->
            <transactionManager type="JDBC"/>
            <!--POOLED代表采取连接池的方式管理连接-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

配置好mybatis的核心配置文件后,我们需要对数据库的每一个表进行配置

image-20210405123047521

首先根据表,创建对应的Bean对象,然后在resources目录下新建mappers文件夹用于映射SQL语句,每一张表对应一个xml文件。

既然是xml文件那自然少不了基本配置,同样在mybatis官网,入门一栏中查找

image-20210405123254925

<?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">
<!--namespace建议和表名一致,因为多个xml可以有多个id为list的select标签,命名空间用作区分-->
<mapper namespace="course">
    <!--id的作用就是通过这个id名找到SQL语句,resultType告诉MyBatis将查询到的数据转为什么类型-->
    <select id="list" resultType="com.harrison.Bean.Course">
        SELECT * FROM course
    </select>
</mapper>

接下来我们需要告诉MyBatis这条语句要怎么找到,也就是找到couse.xml文件

在mybatis-config.xml的中添加mappers标签,指名相对路径,一个xml对应一个mapper

<mappers>
    <mapper resource="mappers/skill.xml"/>
</mappers>

实现查询

在测试类中进行数据查询

public class CourseTest {
    @Test
    public void select() throws Exception {
        try(Reader reader = Resources.getResourceAsReader("mybatis-config.xml")){
            //创建一个工厂构建器
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //创建一个工厂
            SqlSessionFactory factory = builder.build(reader);
            //创建一个Session
            try(SqlSession session = factory.openSession()) {
                //执行SQL语句
                List<Course> courses = session.selectList("course.list");
                for(Course course : courses){
                    System.out.println(course);
                }
            }
        }
    }
}

image-20210405132246726

整体流程总结

  1. 在pom.xml文件中添加依赖(MySQL,mybatis,junit)
  2. 在resources中添加mybatis-config.xml,添加环境,连接数据库,并说明默认环境(development)
  3. 每一张表都要创建对应的bean,并且在mappers目录中创建对应的bean.xml,写上要查询的SQL语句,并将这个xml配置到mybatis-config.xml中
  4. 加载mybatis-config.xml,创建SqlSessionFactoryBuilder工厂构建器类,使用其build方法创建一个工厂SqlSessionfactory,再通过工厂的openSession()创建一个SqlSession,调用SqlSession的selectList或其他方法,传入namespace.id,执行SQL语句

工厂创建注意事项

在官方文档中有这样几句话

image-20210405140230354

为此我们需要对之前的工厂创建进行优化

public class MyBatises {
    private static SqlSessionFactory factory;
    static{
        try(Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
            //创建一个工厂构建器
//            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //创建一个工厂
            factory = new SqlSessionFactoryBuilder().build(reader);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static SqlSession openSession() {
        return factory.openSession();
    }
}

我们将获取Session写入工具类MyBatises中,factory作为单例模式仅会创造一个,并且FactoryBuilder,生产出来factory之后便不再需要了

其他配置

在MyBatis xml配置中可以看到有其他配置

驼峰命名

在Java中变量默认使用的是驼峰命名,而数据库中用的是下划线连接的方式,不配置的话我们在java bean中只能写和数据库一样的名字,不符合Java命名规范

方法一

<settings>
    <!--数据库:my_first_name -> Java: myFirstName-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

方法二

在每张表的xml文件中,修改原来的select标签为以下标签,也就是将resultType更换为resultMap,并在resultMap中对应每一个你想要进行驼峰命名的变量进行一一匹配

<resultMap id="rmCourse" type="com.harrison.Bean.Course">
    <id property="id" column="id" />  <!--主键必须用id-->
    <result property="createdTime" column="created_time"/>
</resultMap>
<select id="list" resultMap="rmCourse">
    SELECT * FROM course
</select>

注意:这个要放在前面,mybatis对标签的顺序有严格的要求,配置环境和mappers一定要依次放在最后

如果你不知道顺序是什么,可以随意写,然后点一下报错的红线就会有提示

SQL查询返回单个对象

<select id="get" resultType="com.harrison.bean.Course">
    SELECT * FROM course WHERE credit = #{grade}
</select>
@Test
public void select2() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        Course course = session.selectOne("course.get", 1);
        System.out.println(course);
    }
}

select标签接收多个SQL语句参数

在xml文件中,如果使用<号,会认为你接下来要输入一个标签,用在SQL中小于号就会报错,所以这个时候需要转为字符实体:&lt; 这样

<select id="list2" resultType="com.harrison.bean.Course">
    SELECT * FROM course WHERE credit > #{credit} AND cname = #{cname}
</select>
@Test
public void select3() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        //Map<String, Object> map = new HashMap<>();
        //map.put("credit", 1);
        //map.put("cname", "数据结构");
        Course map = new Course();
        map.setCname("数据结构");
        map.setCredit(1);
        List<Course> courses = session.selectList("course.list2", map);
        for(Course course : courses) {
            System.out.println(course);
        }
    }
}

我在这里提供了两种方式,创建一个map进行传参,或者使用bean对象直接传参

注意:

  • #{}中间的参数名要和setName的Name一致

  • SQL语句中LIKE之类的,不要写成 %#{name}%,必须要把%M%这种当成一个整体传入#{name}中,否则必须写成%${name}%

  • KaTeX parse error: Expected 'EOF', got '#' at position 5: {} 和#̲{}都可以设置传参,但是建议使…{}是直接拼接字符串, 无法防止SQL注入,即你的条件是SELECT * FROM table WHERE name = ${},

    假如对方填入111 or 1 = 1;你的sql语句就变为SELECT * FROM table WHERE name = 111 or 1 = 1,这种在任何条件都是成立的,别人你就会拿到你的数据。但是#{}就会进行预编译,生成SELECT * FROM table WHERE name = ?,编译器就会检查传来的参数,防止sql注入

  • ${}使用场景,sql语句是代码拼接生成的,不是直接写好传参,如SELECT * FROM table , 有 可 能 会 传 入 O R D E R B Y n a m e , 但 是 如 果 预 编 译 就 会 变 为 S E L E C T ∗ F R O M t a b l e ? , 语 法 就 不 对 , 所 以 这 种 情 况 必 须 使 用 {},有可能会传入ORDER BY name,但是如果预编译就会变为SELECT * FROM table ?,语法就不对,所以这种情况必须使用 ORDERBYnameSELECTFROMtable使{}

打印日志信息

在mybatis-config.xml的settings中添加

<setting name="logImpl" value="STDOUT_LOGGING"/>

或者添加依赖

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

这种的更详细

简单多表查询

<resultMap id="rmExperience" type="com.harrison.bean.Experience">
    <result property="company.name" column="t1_name"/>
    <result property="company.website" column="t1_website"/>
    </resultMap>
    <select id="list2" resultMap="rmExperience">
    SELECT  t2.*,
            t1.name t1_name,
            t1.website t1_website
    FROM company t1 JOIN experience t2
    ON t1.id = t2.company_id;
</select>

建立查询后,会将experience的所有数据和company的name、website进行查询,查询到的所有结果我们会给到resultMap中,进行匹配时就会将type中的Bean对象解读,拿出property中的元素一一对应赋值,看不懂的话看我下面这张图

image-20210405170240505

image-20210405170329473

@Test
public void select2() {
    try(SqlSession session = MyBatises.openSession()) {
        List<Experience> experiences = session.selectList("experience.list2");
        for(Experience experience : experiences) {
            System.out.println(experience);
        }
    }
}

查询结果

Experience{job='学生', intro='张三', beginDay=Tue Apr 06 00:00:00 CST 2021, endDat=null, companyId=1}
Experience{job='老师', intro='李四', beginDay=Tue Apr 06 00:00:00 CST 2021, endDat=null, companyId=3}
Experience{job='教授', intro='王五', beginDay=Thu Apr 22 00:00:00 CST 2021, endDat=null, companyId=3}

更简单的方法可以这样写

<select id="list" resultType="com.harrison.bean.Experience">
    SELECT t2.*,
    t1.name `company.name`,
    t1.website `company.website`
    FROM company t1 JOIN experience t2
    ON t1.id = t2.company_id;
</select>

我们直接将t1.name和t1.website拿出来就给到resultType中Experience这个Bean对象的company属性的name属性以及website属性

@Test
public void select1() {
    try(SqlSession session = MyBatises.openSession()) {
        List<Experience> experiences = session.selectList("experience.list");
        for(Experience experience : experiences) {
            System.out.println(experience);
        }
    }
}

查询结果

Experience{job='学生', intro='张三', beginDay=Tue Apr 06 00:00:00 CST 2021, endDat=null, companyId=1}
Experience{job='老师', intro='李四', beginDay=Tue Apr 06 00:00:00 CST 2021, endDat=null, companyId=3}
Experience{job='教授', intro='王五', beginDay=Thu Apr 22 00:00:00 CST 2021, endDat=null, companyId=3}

优化多表查询

上面有个统一的问题,我们虽然打印出来信息了,但是并没有看到两张表合并的结果,只展示了Experience这个表,这是因为我们拿到的其实是Experience对象,只不过里面有个company,只有在debug的时候才能展示出来,为了能直接打印出来,我们将查询到的结果转为LinkedHashMap(普通的Map展示的时候每个属性和当初建表的时候顺序是不一样的,因为有Hash所以是随机分布),我们再来看结果

image-20210405171342218

<select id="list3" resultType="java.util.LinkedHashMap">
    SELECT 
        t2.*,
        t1.name `company.name`,
        t1.website `company.website`
    FROM company t1 
    JOIN experience t2 ON t1.id = t2.company_id;
</select>
@Test
public void select3() {
    try(SqlSession session = MyBatises.openSession()) {
        List<Map<String, Object>> experiences = 		 session.selectList("experience.list3");
        for(Map<String, Object> experience : experiences) {
            System.out.println(experience);
        }
    }
}

查询结果

image-20210405171638215

所以最终方案我是建议用优化后的方案来写的

添加操作

<insert id="add1" parameterType="com.harrison.bean.Skill">
    INSERT INTO skill (name, level) VALUES(#{name}, #{level});
</insert>

parameterType:对于有传参的操作,建议制定传参的类型,防止别人乱传参数,之前的select也可以加

MyBatis中的添加操作需要手动提交事务,否则会自动回滚

session.commit(),或者在创建session时修改

//在创建session时传入参数,修改为默认自动提交事务
    public class MyBatises {
    private static SqlSessionFactory factory;
    static{
        try(Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
            //创建一个工厂构建器
//            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //创建一个工厂
            factory = new SqlSessionFactoryBuilder().build(reader);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static SqlSession openSession() {
        return factory.openSession(true);  //在这里添加true
    }
}

设置完成后运行程序

@Test
public void insert1() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        Skill skill = new Skill("Java", 666);
        session.insert("skill.add1", skill);
        //没有设置true则需在这里添加
        //session.commit()
    }
}

获取新插入数据的主键

当我们想要在插入数据后立马拿到这个数据的主键用来进行其他操作应该如何操作?

第一种方案是查询最后一条语句,拿到id,但是弊端在于多线程的话可能还没有来得及查询就又插进来一条数据

第二种方案:

<insert id="add2" parameterType="com.harrison.bean.Skill">
    INSERT INTO skill(name, level) VALUES (#{name}, #{level})
    <!--查询最后一条记录的id值,这个值是int类型,并且返回给skill类的id属性,在插入后执行-->
    <selectKey resultType="int" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

添加selectKey标签,在keyProperty属性中说明查到的内容给回到Bean的哪个属性中,这样查询id后会立马setId()给到skill对象

@Test
public void insert2() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        Skill skill = new Skill("ios", 999);
        session.insert("skill.add2", skill);
        System.out.println(skill.getId());
        session.commit();
    }
}

第三种方案:

<insert id="add3"
        parameterType="Skill"
        useGeneratedKeys="true"
        keyProperty="id">
    INSERT INTO skill(name, level) VALUES (#{name}, #{level})
</insert>

直接添加useGeneratedKeys="true"和keyProperty="id"两个属性

@Test
public void insert3() throws Exception {
    try(SqlSession session = MyBatises.openSession()){
        Skill skill = new Skill("Go", 90);
        session.insert("skill.add3", skill);
        System.out.println(skill.getId());
        session.commit();
    }
}

缺点:MySQL可以用,Oracle用不了,驱动不支持

动态SQL

if标签

我们先来看一个多条件查询

SELECT * FROM skill 
WHERE id > #{id}
AND name LIKE #{name}
AND level &lt; #{level}

如果我们希望当其中某个条件传入了参数这个条件才生效,我们可以使用MyBatis提供的动态SQL

<select id = "dynamic2" parameterType="Map" resultType="com.harrison.bean.Skill">
    SELECT * FROM skill WHERE 1 = 1
    <if test="id1 != null">
        AND id > #{id1}
    </if>
    <if test="name1 != null">
        AND name LIKE #{name1}
    </if>
    <if test="level1 != null">
        AND level &lt; #{level1}
    </if>
</select>

注意我这里写的,if中的变量名不是数据库的变量名,而是${}中的名字

这里必须加上where 1 = 1防止三个变量都没有传入时,SQL语法错误导致报错

@Test
public void dynamicSql2() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        Map<String, Object> map = new HashMap<>();
        map.put("id1", 5);
        //            map.put("name", "%o%");
        map.put("level1", 6666);
        List<Skill> skills = session.selectList("skill.dynamic2", map);
        for(Skill skill : skills) {
            System.out.println(skill);
        }
    }
}

有没有办法可以不写where 1 = 1

有!采用where标签

where标签

<select id = "dynamic3" parameterType="Map" resultType="com.harrison.bean.Skill">
    SELECT * FROM skill
    <where>
        <if test="id1 != null">
            AND id > #{id1}
        </if>
        <if test="name1 != null">
            AND name LIKE #{name1}
        </if>
        <if test="level1 != null">
            AND level &lt; #{level1}
        </if>
    </where>
</select>

很智能,如果参数都没有传进来就生成SELECT * FROM skill,如果传入参数的话,会把第一个语句的AND去掉,还记得上面讲的打印日志信息吗,自己动手试一下,试试自己在日志中查看生成的sql语句吧

foreach标签

批量添加

INSERT INTO skill(name, level)
VALUES('Jack1', 10), ('Jack2', 40), ('Jack3', 20), ('Jack4', 14);

批量删除

DELETE FROM skill WHERE id in (1, 2, 3, 4);

MyBatis中实现批量添加

<insert id="batchInsert" parameterType="List">
    INSERT INTO skill(name, level) VALUES
    <foreach collection="list" item="skill" separator=",">
        (#{skill.name}, #{skill.level})
    </foreach>
</insert>

collection根据传进来的参数决定,如果collection是List就可以接收一个List,如果是Array就可以接收一个Array

item代表每次遍历时的变量名

separator表示一句结束后的分隔符

@Test
public void batchInsert() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        List<Skill> skills = new ArrayList<>();
        skills.add(new Skill("Java1", 111));
        skills.add(new Skill("Java2", 222));
        skills.add(new Skill("Java3", 333));
        skills.add(new Skill("Java4", 444));

        session.insert("skill.batchInsert", skills);
        session.commit();
    }
}

MyBatis中实现批量删除

<delete id="batchDelete" parameterType="List">
    DELETE FROM skill WHERE id IN (
    <foreach collection="list" item="id" separator=",">
        #{id}
    </foreach>
    )
</delete>

这两个括号看起来好难受…我们用openc和close语句去掉它

<delete id="batchDelete2" parameterType="List">
    DELETE FROM skill WHERE id IN 
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>
@Test
public void batchDelete() throws Exception {
    try(SqlSession session = MyBatises.openSession()) {
        List<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        ids.add(4);

        session.insert("skill.batchInsert2", ids);
        session.commit();
    }
}

批量操作的局限性:无法通过获取操作后数据的id值

但是可以使用useGeneratedKey="id"获取主键

typeAliases标签

在mybatis-config.xml中添加别名

<typeAliases>
    <!--一旦设置了别名,不区分大小写-->
    <typeAlias type="com.harrison.bean.Skill" alias="skill"/>
    <!--这个包下的所有类,都会自动取别名为短类名,即skill-->
    <package name="com.harrison.bean"/>
</typeAliases>

我们之前在写resultType等其他属性时,经常会写全类名(com.harrison.bean.Skill),这样是非常累的,所以我们可以给它设置个别名,之后直接调用这个别名就可以了

如果像Java bean这种对象,直接用package,会自动取短类名,更方便

sql标签

sql标签可以用来抽取公共sql语句

image-20210406003710257

constructor标签

在resultMap标签中我们会设置属性type=“Skill”,诸如此类,说明查询以后会new一个Skill对象来存放数据,默认是会调用无参构造方法来创建对象,如果我们希望调用有参构造方法,那么就可以在resultMap标签中使用constructor标签

<resultMap id = "rmGet" type = "Skill">
	<constructor>
        <!--如果有主键就添加idArg-->
        <idArg name="id" column="id"/>
 		<arg name="name" column="name"/>
        <arg name="level" column="level"/>
    </constructor>
</resultMap>

集成Druid连接池

之前在mybatis-config.xml中配置的是mybatis内置的连接池,但是现在一般都使用效率更快的Druid或者c3p0连接池,因为他们更专业

  • 首先在pom.xml中添加依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.5</version>
    </dependency>
    
  • 紧接着,我们新建一个目录存放Druid相关类

image-20210406005134246

Mybatis中有一个连接池,它允许其他连接池作为它的连接池,但是需要继承PooledDataSourceFactory类并且给dataSource变量赋值

public class DruidDataSourceFactory extends UnpooledDataSourceFactory {
    public DruidDataSourceFactory() {
        this.dataSource = new DruidDataSource();
    }
}
  • 然后在mybatis-config.xml中添加一个新的环境(environment)

    <!-- Druid连接池 -->
    <environment id="development">
        <!--采用JDBC的事务管理方法-->
        <transactionManager type="JDBC"/>
        <!--type填写Druid类,这个类名也能起一个别名-->
        <dataSource type="com.harrison.common.DruidDataSourceFactory">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/test-mybatis"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
            <property name="initialSize" value="5"/>
            <property name="maxActive" value="10"/>
            <property name="maxWait" value="5000"/>
        </dataSource>
    </environment>
    

    不便于修改?想自己写一个资源文件统一管理?

<?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 resource="druid.properties" />
 
     <!-- 其他设置 -->
     <settings>
         <setting name="mapUnderscoreToCamelCase" value="true"/>
         <!-- true代表所有设置了select属性的关联对象都延迟加载 -->
         <setting name="lazyLoadingEnabled" value="true"/>
     </settings>

     <!-- 别名 -->
   <typeAliases>
         <!-- 一旦设置了别名,它是不区分大小写的 -->
         <typeAlias type="com.mj.common.DruidDataSourceFactory" alias="druid" />
     </typeAliases>
 
     <!-- 环境 -->
     <environments default="development">
         <!-- 开发环境(调试阶段) -->
         <environment id="development">
             <!-- 采用JDBC的事务管理方法 -->
             <transactionManager type="JDBC" />
 
             <!-- POOLED代表采取连接池的方式管理连接 -->
             <dataSource type="DRUID">
                 <property name="driverClassName" value="${dev.driverClass}"/>
                 <property name="url" value="${dev.url}"/>
                 <property name="username" value="${dev.username}"/>
                 <property name="password" value="${dev.password}"/>
                 <property name="initialSize" value="${dev.initialSize}"/>
                 <property name="maxActive" value="${dev.maxActive}"/>
                 <property name="maxWait" value="${dev.maxWait}"/>
             </dataSource>
         </environment>
     </environments>
 
     <!-- 映射文件 -->
     <mappers>
         <mapper resource="mappers/person.xml"/>
         <mapper resource="mappers/idCard.xml"/>
         <mapper resource="mappers/bankCard.xml"/>
         <mapper resource="mappers/job.xml"/>
     </mappers>
 </configuration>

我们可以在resources目录下新建一个druid.properties,上面的properties标签会自动到resources目录下查找这个文件,找到了,会解析里面的内容转换为下面注释的格式,后面的代码就可以通过value="${name}"拿到properties的value值了

// druid.properties文件内容
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test-mybatis
username=root
password=root
initialSize=5
maxActive=10
maxWait=5000

对这个配置文件我们还可以通过前缀来区分测试版本连接池和发布版本连接池

dev.driverClassName=com.mysql.jdbc.Driver
dev.url=jdbc:mysql://localhost:3306/test-mybatis
dev.username=root
dev.password=root
dev.initialSize=5
dev.maxActive=10
dev.maxWait=5000

pro.driverClassName=com.mysql.jdbc.Driver
pro.url=jdbc:mysql://localhost:3306/test-mybatis
pro.username=root
pro.password=root
pro.initialSize=5
pro.maxActive=10
pro.maxWait=5000

这些操作都进行完毕后,mybatis就会创建一个DruidDataSourceFactory对象,读取下面写的配置,并且赋值给刚才写好的那个dataSource数据源

数据库分页查询引发的问题

mysql分页查询

SELECT * FROM student LIMIT 10, 10;

Oracle分页查询

SELECT * FROM
	(
        SELECT
        	s.*, ROWNUM rn
        FROM
        	(SELECT * FROM student) s
        WHERE ROWNUM <= 20
    )
WHERE rn >= 11;

SQL Server分页查询

SELECT * FROM student
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;

我们在编写MyBatis时遇到分页查询,针对不同的数据库就需要编写不同的代码,非常的不友好—>解决方案:PageHelper

PageHelper

配置依赖

首先要增加Maven依赖

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

然后在mybatis核心配置文件mybatis-config.xml中添加拦截器

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--true代表分页合理化:pageNum <= 0就会自动获取第一页,pageNum > pages就会自动获取最后一页,还有很多属性可以修改,但是一般默认值就是最常用的,如果想要修改可以去github上查看-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

运用

未使用pageHelper前:

<select id="list" parameterType="skill" resultType="Skill">
    <include refid="sqlListAll" /> LIMIT 0, 10
</select>

使用pageHelper后:

<select id="list" parameterType="skill" resultType="Skill">
    <include refid="sqlListAll" /> LIMIT 0, 10
</select>
@Test
public void page() throws Exception{
    try(SqlSession session = MyBatises.openSession()) {
        PageHelper.startPage(1, 10);
        List<Skill> skills = session.selectList("skill.list");
        for(Skill skill: skills) {
            System.out.println(skill);
        }
    }
}

我们可以在日志中查看信息

image-20210412211046395

前面配置的拦截器类似于Filter,当我们需要用到查询语句时,Mybatis会先把SQL语句拼接起来,这时,拦截器会将其拦截掉,加入我们提前写好的PageHelper.startPage(1, 10);语句中的SQL语句拼接到SELECT * FROM skill后面,并传入相应的参数

多表查询

数据库设计

# drop
DROP TABLE IF EXISTS bank_card;
DROP TABLE IF EXISTS id_card;
DROP TABLE IF EXISTS person_job;
DROP TABLE IF EXISTS person;
DROP TABLE IF EXISTS job;

# person
CREATE TABLE person(
	id INT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(20) NOT NULL
);

# bank_card
CREATE TABLE bank_card(
	id INT PRIMARY KEY AUTO_INCREMENT,
	no VARCHAR(30) NOT NULL UNIQUE,
	amount DECIMAL(18, 2) NOT NULL,
	person_id INT NOT NULL,
	FOREIGN KEY (person_id) REFERENCES person(id)
);

# id_card
CREATE TABLE id_card(
	id INT PRIMARY KEY AUTO_INCREMENT,
	no VARCHAR(30) NOT NULL UNIQUE,
	address VARCHAR(50) NOT NULL,
	person_id INT NOT NULL UNIQUE,
	FOREIGN KEY (person_id) REFERENCES person(id)
);

# job
CREATE TABLE job(
	id INT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(20) NOT NULL UNIQUE,
	duty VARCHAR(50) NOT NULL
);

# person_job
CREATE TABLE person_job(
	person_id INT,
	job_id INT,
	PRIMARY KEY (person_id, job_id),
	FOREIGN KEY (person_id) REFERENCES person(id),
	FOREIGN KEY (job_id) REFERENCES job(id)
);

# data
INSERT INTO person(name) VALUES ('Jack'), ('Rose'), ('Larry'), ('Mike'), ('Tom'), ('James');

INSERT INTO id_card(no, address, person_id) VALUES 
('9527', '北京', 4),
('8866', '广州', 1),
('2495', '上海', 5),
('4378', '成都', 2),
('5454', '杭州', 6),
('9923', '深圳', 3);

INSERT INTO bank_card(no, amount, person_id) VALUES 
('6223', 0, 1),
('75556', 2098.56, 2),
('5345', 1010000.56, 1),
('87876', 534423.34, 3),
('654645', 432.45, 1),
('5434534', 234765.19, 4),
('76853', 98945.39, 4),
('6456867', 435534.78, 1),
('4324654', 874343.99, 4),
('53455', 5.20, 2);

INSERT INTO job(name, duty) VALUES 
('程序员', '每一天都在写新的bug和修改昨天的bug'),
('保安', '公司全系统物理安全保障专员'),
('网管', '世界互联网信息终端及人类信息科技部信息集成应用导师'),
('厨师', '类口腔神经末梢感应实验中心及绿色环保邮寄肥转换加工基地负责人'),
('贴膜', '智能高端移动设备表面高化合物平面处理'),
('搬砖', '长方体混泥土瞬间移动师'),
('算命', '主观性逻辑推论及心理引导'),
('理发师', '人体无用副组织切除手术主刀');

INSERT INTO person_job(person_id, job_id) VALUES 
(1, 1),
(1, 3),
(1, 5),
(1, 7),
(2, 5),
(3, 1),
(3, 2),
(5, 3),
(5, 5),
(5, 7);

Java Bean设计

public class Person {
    private Integer id;
    private String name;
    private IdCard idCard;
    private List<BankCard> bankCards;
    private List<Job> jobs;
}
public class IdCard {
    private Integer id;
    private String no;
    private String address;
    private Person person;
}
public class Job {
    private Integer id;
    private String name;
    private String duty;
    private List<Person> persons;
}
public class BankCard {
    private Integer id;
    private String no;
    private BigDecimal amount;
    private Person person;
}

多表关系

我们在简单多表查询中介绍了一种简单的方法,无需使用resultMap,直接通过取别名的方式,如SELECT p.*, ic.id ‘idCard.id’…通过单引号指出将查到的结果送入idCard对象的id属性,然而一些时候我们更多的是采用resultMap手动完成映射

一对一

person与id_card的关系就是一对一,一个人只有一个身份证,一个身份证也只对应一个人,所以这里外键person_id一定是UNIQUE唯一的

<sql id="sqlListAll">
    SELECT
        p.*,
        ic.id ic_id,
        ic.no ic_no,
        ic.address ic_address
    FROM
    	person p
    JOIN id_card ic ON p.id = ic.person_id
</sql>
<resultMap id="rmList" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="idCard" javaType="IdCard">
        <id property="id" column="ic_id"/>
        <result property="no" column="ic_no"/>
        <result property="address" column="ic_address"/>
    </association>
</resultMap>

<select id="list" resultMap="rmList">
    <include refid="sqlListAll"/>
</select>

我们现在要查询people的所有信息以及身份证的id, no, address信息

数据库设计中,我们让id-card拥有一个外键person_id,在java Bean的设计中,我们采用类似的方案,因为是一对一的关系,我们让Person对象拥有一个IdCard对象,如果是一对多的关系我们就要拥有一个IdCard的list链表存放多个。

接下来,我们要在resultMap中阐明映射关系,在之前我们讲过了简单的映射关系,由于我们现在在Person中引入了IdCard类,简单的映射无法告知MyBatis查出来的值是属于哪个类的,因此,我们添加一个association标签用来表示关联关系

如上所示,简单翻译一下这个xml

  • 我们通过select进行查询,查询的语句在include标签中引用,返回的结果按照resultMap中给出的映射关系进行映射
  • 在resultMap中这样定义:我们查询到的数据是type="Person"类型的,这个数据中有id和result(MyBatis官方文档中建议在有id主键时使用id标签,这会在一些特殊情况中提高查询效率),其中id或者result标签的property表示的是Person类的属性,column表示数据库所要查询的属性,由于我们的Person关联着一个IdCard表,所以我们需要使用association标签表示关联关系,里面的内容和外面的大同小异
一对多/多对一

一对多与一对一不同的地方在于我们的外键不能设为唯一的,比如说person和bank_card,人和银行卡就是一对多的关系,一个人有多个银行卡,但是一个银行卡只对应一个人

此时,我们的外键就不应该是UNIQUE唯一的,一个人不应该只拥有一张银行卡

同时,为了表示多张银行卡,我们不能简单的使用association标签,而应该使用collection标签来表示一对多

<sql id="sqlListAll2">
    SELECT
        p.*,
        ic.id id_card_id,
        ic.no id_card_no,
        ic.address id_card_address,
        bc.id bank_card_id,
        bc.no bank_card_no,
        bc.amount bank_card_amount
    FROM
        person p
    JOIN id_card ic ON p.id = ic.person_id
    LEFT JOIN bank_card bc ON p.id = bc.person_id
</sql>
<resultMap id="rmList2" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="idCard" javaType="IdCard">
        <id property="id" column="id_card_id"/>
        <result property="no" column="id_card_no"/>
        <result property="address" column="id_card_address"/>
    </association>
    <collection property="bankCards" ofType="BankCard">
        <id property="id" column="bank_card_id"/>
        <result property="no" column="bank_card_no"/>
        <result property="amount" column="bank_card_amount"/>
    </collection>
</resultMap>
<select id="list3" resultMap="rmList2">
    <include refid="sqlListAll2"/>
</select>

特别强调:由于内连接(JOIN)只会将符合ON条件的数据保留,但是我们希望将所有人的信息都展示出来,哪怕这个人没有银行卡,所以我们需要使用左连接(LEFT JOIN)将没有银行卡的人也找出来

多对多

一个人对应多种职业(兼职),一个职业也能对应多个人

Java Bean对应的属性就需要一个List来存放

数据库设计,job表和person表之间没有任何关系,所以我们采用一个person_job表作为连接两个表之间的桥梁

<sql id="sqlManyToMany">
    SELECT
        p.*,
        ic.id id_card_id,
        ic.no id_card_no,
        ic.address id_card_address,
        bc.id bank_card_id,
        bc.no bank_card_no,
        bc.amount bank_card_amount,
        j.id job_id,
        j.name job_name,
        j.duty job_duty
    FROM
        person p
    JOIN id_card ic ON p.id = ic.person_id
    LEFT JOIN bank_card bc ON p.id = bc.person_id
    LEFT JOIN person_job pj ON p.id = pj.person_id
    LEFT JOIN job j ON j.id = pj.job_id
</sql>
<resultMap id="rmManyToMany" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="duty" column="duty"/>
    <association property="idCard" javaType="IdCard">
        <id property="id" column="id_card_id"/>
        <result property="no" column="id_card_no"/>
    </association>
    <collection property="bankCards" ofType="BankCard">
        <id property="id" column="bank_card_id"/>
        <result property="no" column="bank_card_no"/>
        <result property="amount" column="bank_card_amount"/>
    </collection>
    <collection property="jobs" ofType="Job">
        <result property="name" column="job_name"/>
        <result property="duty" column="job_duty"/>
    </collection>
</resultMap>
<select id="many" resultMap="rmManyToMany">
    <include refid="sqlManyToMany"></include>
</select>
@Test
public void list() throws Exception{
    try(SqlSession session = MyBatises.openSession()) {
        List<Person> persons = session.selectList("person.many");
        System.out.println(persons);
    }
}

延迟加载

多表查询的问题:查person时我们把银行卡,职业这些集合类型的数据一次性全部都查出来了,但是不一定立马就需要,所以我们需要采用延迟加载的方法,等到需要的时候再加载

这样做不会造成资源浪费,但是需要多条SQL语句

什么时候使用立即加载:如果不是集合类型(身份证这种一对一的),就可以立即加载

在MyBatis中,关联对象association、collection可以实现延迟加载

但是一般就给collection设置延迟加载,因为association只是单个对象,不会太大

<sql id="sqlLazy">
    SELECT * FROM person
</sql>
<resultMap id="rmLazy" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="idCard" javaType="IdCard"
                 fetchType="lazy"
                 column="id"
                 select="idCard.getByPerson"/>
    <collection property="bankCards" ofType="BankCard"
                fetchType="lazy"
                column="id"
                select="bankCard.listByPerson"/>
    <collection property="jobs" ofType="Job"
                fetchType="lazy"
                column="id"
                select="job.getByPerson"/>
</resultMap>
<select id="lazy" parameterType="int" resultMap="rmLazy">
    <include refid="sqlLazy"/> WHERE id = #{id}
</select>

idCard.xml

<!--通过Person的id查询对应的IdCard-->
<select id="getByPerson" parameterType="int" resultType="IdCard">
    SELECT * FROM id_card WHERE person_id = #{personId}
</select>

bankCard.xml

<select id="listByPerson" parameterType="int" resultType="BankCard">
    SELECT * FROM bank_card WHERE person_id = #{personId}
</select>

job.xml

<select id="getByPerson" parameterType="int" resultType="Job">
    SELECT
        j.*	
    FROM
        job j
    JOIN person_job pj ON
    	j.id = pj.job_id AND pj.person_id = #{personId}
</select>
@Test
public void lazy() throws Exception{
    try(SqlSession session = MyBatises.openSession()) {
        Person persons = session.selectOne("person.lazy", 1);
        List<BankCard> cards = persons.getBankCards();
        for(BankCard card : cards) {
            System.out.println(card.getAmount());
        }
    }
}

我们想要查询Person对象的属性,person中的id_card以及bank_card和job都需要延迟加载,那么我们可以将它们的resultMap映射关系都写成但标签,在collection、association标签中添加属性fetchType='lazy'意为延迟加载,之后使用select属性说明当我们需要加载的时候,即在运行时调用了person.getbankCard()之类的函数,我们应该去哪里查询sql语句,因此,我们需要在各自的xml文件中都添加id为getByPerson的select标签,这个id就作为延迟加载时select属性要填写的id。

这个select属性还要搭配column属性使用,因为延迟查询时我们的sql语句是有条件的,所以我们要传入person的id,这个id值在我们的person.xml文件中是立即查询的,我们把查到的字段选一个送入select属性对应的sql语句中,很明显这里需要的是查到的person的id字段,所以column需要填写id

fetchType="eager"意为立即加载,我们可以直接在SQL语句中就立即查询,也可以通过select属性执行另一条SQL语句进行立即查询

全局延迟加载开关

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

我们在mybatis-config.xml文件中添加如上配置,那么就可以不去写fetchType=“lazy”,

这个设置会将所有设置了select属性的关联对象都延迟加载

缓存

缓存:是指为了减少数据库直接访问次数,提高访问效率,而临时存储在内存中的数据

适合放缓存中的数据:

  • 经常查询
  • 不经常改变
  • 数据的正确性对最终结果影响不大

不适合放缓存中的数据:

  • 商品库存
  • 股票、黄金的价格、汇率

MyBatis的缓存

MyBatis的缓存存放的是select取出的数据

一级缓存

一级缓存是存放到了SqlSession对象中

  • 同一个SqlSession的select共享缓存
  • 所以当关闭SqlSession时,缓存也就失效了
  • 执行Insert、update、delete、commit、等方法时,会自动清理一级缓存
  • 由于在很多时候,每次查询用的都是不同的SqlSession,所以一级缓存的命中率不高
二级缓存

为了提高缓存的命中率,可以考虑开启MyBatis的二级缓存,它是namespace(mapper)级别的缓存

二级缓存放在我们在MyBatises类中的factory对象中,全局唯一

  • 同一个namespace下的select共享缓存
  • 默认情况,namespace下update、insert、delete执行成功时,会自动清理该namespace下的二级缓存
开启二级缓存

未开启二级缓存:

<select id="list" parameterType="int" resultType="Job">
    SELECT * FROM job WHERE id = #{id}
</select>
@Test
public void lazy() throws Exception{
    try(SqlSession session = MyBatises.openSession()) {
        Job job1 = session.selectOne("job.list", 1);
        System.out.println(job1);
    }
    try(SqlSession session = MyBatises.openSession()) {
        Job job2 = session.selectOne("job.list", 1);
        System.out.println(job2);
    }
}

输出结果中我们可以看到,尽管查询出来的是相同的内容,但是我们没有开启二级缓存,所以尽管一级缓存可以保存信息,但是仅在SqlSession作用域内,我们在示例代码中开启了两个SqlSession,所以会看到两个地址

image-20210411164321297

开启二级缓存:

mybatis-config.xml文件中添加settings

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

job.xml文件中mapper中添加cache标签,默认会缓存映射文件中的所有select标签的结果

cache标签的常用属性:

  • eviction:缓存清除策略,可选值有LRU(默认值)、FIFF、SOFT、WEAK

  • size:缓存多少个存储结果(单个对象或一个列表)的引用,默认值是1024

  • flushInterval:每隔多少毫秒清除一次缓存,默认不会定时清除缓存

  • readOnly:true代表缓存的是对原对象的引用,false代表缓存的是原对象序列化后的拷贝对象,所以false时要求Java Bean对象实现Serializable接口,默认值时false

  • true选项是不安全的,当我们有多个SqlSession查询时,在内部对job进行修改,比如job.setName(),由于引用的是原对象,在同一namespace下的其他SqlSession中也会进行修改,假如我们不希望这样做,那就直接按照readOnly的默认值进行配置即可

总结:对Java Bean对象实现Serializable接口,并在bean.xml文件中的mapper标签内直接写<cache/>即可

放入二级缓存时机

session.close()

当开启了二级缓存后,session对象关闭时会将select的结果放入缓存中,未关闭前的结果将影响缓存结果,close后再修改则不会影响缓存

useCache

使用这个属性的前提条件是在settings中开启全局二级缓存

我们可以在select标签中可以添加useCache="false"可以关闭单个select的二级缓存

当进行分页查询时,条件很多,查询的结果也很多,二级缓存命中率其实是不高的,这种情况就没必要开启这一个select的二级缓存了

flushCache
<update id="update" flushCache="false" parameterType="Job">
	UPDATE job SET name = #{name}, duty = #{duty} WHERE id = #{id}
</update>

update标签执行后默认会清除缓存,如果我们不希望清除缓存,可以使用flushCache="false"告诉它不要清除缓存,select标签默认就不会清除缓存,所以不用加

Dao层

使用MyBatis实现dao层

  • 自定义dao实现类,在实现中调用SqlSession的相关方法(使用XML)
  • 只定义dao接口类,SqlSession的getMapper方法生成dao的代理对象(使用XML)
  • 只定义dao接口类,SqlSession的getMapper方法生成dao的代理对象(使用注解)
自定义dao实现类
image-20210412173336346

这种方法只是将之前的代码抽象出来分成接口和实现类

public interface SkillDao {
     boolean save(Skill skill);
     boolean update(Skill skill);
     boolean remove(Integer id);
     Skill get(Integer id);
     List<Skill> list();
}
public class SkillDaoImpl implements SkillDao {
    @Override
    public boolean save(Skill skill) {
        try(SqlSession session = MyBatises.openSession(true)) {
            return session.insert("skill.save", skill) > 0;
        }
    }
    @Override
    public boolean update(Skill skill) {
        try(SqlSession session = MyBatises.openSession(true)) {
            return session.insert("skill.update", skill) > 0;
        }
    }
    @Override
    public boolean remove(Integer id) {
        try(SqlSession session = MyBatises.openSession(true)) {
            return session.insert("skill.remove", id) > 0;
        }
    }
    @Override
    public Skill get(Integer id) {
        try(SqlSession session = MyBatises.openSession(true)) {
            return session.selectOne("skill.get", id);
        }
    }
    @Override
    public List<Skill> list() {
        try(SqlSession session = MyBatises.openSession(true)) {
            return session.selectList("skill.list");
        }
    }
}
getMapper + XML实现dao层

配置要求

  • mapper的namespace必须是dao接口类的全类名

image-20210412174049194

  • mapper中的select、update、insert、delete的id值必须和dao的方法名一致

image-20210412174115206

image-20210412174204862

如果update、insert、delete方法的返回值是Boolean类型

  • 代理对象内部是影响记录数大于0就返回true

这种方法无需自己写impl实现类,通过session.getMapper构造代理对象

当输入dao.get(1),MyBatis就会通过代理对象传入的SkillDao.class找到SkillDao,然后查找get方法,最后去mapper中找到id为get的select标签执行SQL语句

@Test
public void get(){
    try(SqlSession session = MyBatises.openSession()) {
        SkillDao dao = session.getMapper(SkillDao.class);
        System.out.println(dao.get(1));
    }
}
getMapper + 注解实现Dao层

无需xml和impl,只需在SkillDao中添加注解

public interface SkillDao {
    @Insert("INSERT INTO skill(name, level) VALUES (#{name}, #{level})")
    boolean save(Skill skill);
    @Update("UPDATE skill SET name = #{name}, level = #{level} WHERE id = #{id}")
    boolean update(Skill skill);
    @Delete("DELETE FROM skill WHERE id = #{id}")
    boolean remove(Integer id);
    @Select("SELECT * FROM skill WHERE id = #{id}")
    Skill get(Integer id);
    @Select("SELECT * FROM skill")
    List<Skill> list();
}

复杂的SQL语句还是要通过xml执行

去除SSL警告

我们就在每次执行完后会爆出这样的警告

image-20210412174722509

要求我们的MySql连接是安全的,我们目前没有设置SSL协议,只需要在duid.properties配置文件中添加useSSL=false即可

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test-mybatis?useSSL=false
username=root
password=root
initialSize=5
maxActive=10
maxWait=5000

注解

@SelectKey

获取最后插入的数据的主键可以使用@SelectKey,before表示是否在@Insert注解中的SQL语句之前执行

@Insert("INSERT INTO skill(name, level) VALUES (#{name}, #{level})")
@SelectKey(statement = "SELECT LAST_INSERT_ID()",
           keyProperty = "id", before = false,
           resultType = Integer.class)
boolean save(Skill skill);

等同于在xml文件中写

<insert id="add2" parameterType="com.harrison.bean.Skill">
    INSERT INTO skill(name, level) VALUES (#{name}, #{level})
    <!--查询最后一条记录的id值,这个值是int类型,并且返回给skill类的id属性,在插入后执行-->
    <selectKey resultType="int" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

之前讲解过,MySQL中可以使用useGeneratedKey代替的功能

<insert id="add3"
        parameterType="Skill"
        useGeneratedKeys="true"
        keyProperty="id">
    INSERT INTO skill(name, level) VALUES (#{name}, #{level})
</insert>

@Options

在注解中,同样可以使用@Options来实现添加其他的属性

@Options(useGeneratedKeys = true, keyProperty = "id")

@Params

下面的代码中我们希望参数中的start和size可以传入注解中,但是编译器在编译时并不会将变量名进行保存,所以无法识别注解中的start和size是什么,所以我们需要在变量前加上@Param(“start”)告诉编译器这个一会儿要传给注解

@Select("SELECT * FROM skill LIMIT #{start}, #{size}")
List<Skill> listByStartAndSize(@Param("start") int start, @Param("size") int size);
@Test
public void listByStartAndSize(){
    try(SqlSession session = MyBatises.openSession(true)) {
        SkillDao dao = session.getMapper(SkillDao.class);
        List<Skill> skills = dao.listByStartAndSize(1, 4);
        for(Skill skill: skills) {
            System.out.println(skill);
        }
    }
}

@CacheNamespace

二级缓存如何开启?

直接给SkillDao整个接口添加注解@CacheNamespace

@CacheNamespace(flushInerval = 600000, size = 523, eviction = FifoCache.class

@ConstructorArgs

之前我们介绍过,如果想调用对象的有参构造方法,可以使用constructor标签

<resultMap id = "rmGet" type = "Skill">
	<constructor>
        <!--如果有主键就添加idArg-->
        <idArg name="id" column="id"/>
 		<arg name="name" column="name"/>
        <arg name="level" column="level"/>
    </constructor>
</resultMap>

对应的注解(注意:没有@IdArg)

@ConstructorArgs({
    @Arg(column = "name", name = "name")
    @Arg(column = "name", name = "name")
})

关联对象

现在我们将关联对象改为注解的形式

<resultMap id="rmList2" type="Person">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="idCard" javaType="IdCard">
        <id property="id" column="id_card_id"/>
        <result property="no" column="id_card_no"/>
        <result property="address" column="id_card_address"/>
    </association>
    <collection property="bankCards" ofType="BankCard">
        <id property="id" column="bank_card_id"/>
        <result property="no" column="bank_card_no"/>
        <result property="amount" column="bank_card_amount"/>
    </collection>
</resultMap>
@Select("SELECT * FROM person WHERE id = #{id}")
@Results(id = "get" value = {
    @Result(property = "id", column = "id", id = true),
    @Result(property = "name", column = "name"),
    /*身份证*/
    @Result(property = "idCard", 
            column = "id",
            one = @One(fetchType = FetchType.LAZY, select = "com.harrison.dao.IdCardDao.listByPerson")),
    /*银行卡*/
    @Result(property = "bankCards", 
            column = "id",
            many = @Many(fetchType = FetchType.LAZY, select = "com.harrison.dao.BankcardDao.listByPerson")),
    /*工作*/
    @Result(property = "jobs", 
            column = "id",
            many = @Many(fetchType = FetchType.LAZY, select = "com.harrison.dao.JobDao.listByPerson"))
})
@ResultMap("get") //引用写好的@Results
Person get(Integer id);

分别在JobDao、BankCardDao、IdCardDao中添加SQL语句

public void IdCardDao{
    @Select("SELECT * FROM id_card WHERE person_id = #{personId}")
    IdCard getByPerson(Integer personId);
}
public void BankCardDao{
    @Select("SELECT * FROM bank_card WHERE person_id = #{personId}")
    List<BankCard> listByPerson(Integer personId);
}
public void JobDao{
    @Select("SELECT j.* FROM job j"
            + JOIN person_job pj ON j.id = pj.job_id AND pj.person_id = #{personId}")
    List<Job> listByPerson(Integer personId);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值