Mybatis

Mybatis 简化JDBC 的操作,SQL 与代码分离,提高维护性。

1. 环境搭建

Maven 项目,mysql 数据库。

1.1 导入依赖

pom.xml

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

1.2 创建Dao 接口及实体类

AccountMapper

public interface AccountMapper {
    List<Account> getAccountList();
}

Account

public class Account {
    private int id;
    private String name;
    private BigDecimal balance;
	// 构造器及SetGet方法略
	...
}

1.3 创建映射 mapper

相当于Dao 接口的实现类,写Sql 语句的地方。
src\main\java\com\chengyu\dao\AccountMapper.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.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="com.chengyu.pojo.Account">
        select * from book.account
    </select>
</mapper>

1.4 配置Mybatis 核心配置文件

配置文件名称任意。
src\main\resources\mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- 核心配置文件 -->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/book?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="tiger"/>
            </dataSource>
        </environment>
    </environments>
    <!--注册 mapper-->
    <mappers>
        <mapper resource="com/chengyu/dao/AccountMapper.xml"/>
    </mappers>
</configuration>

1.5 创建MybatisUtils 类

创建Modul 后,新建MybatisUtils 类。
src\main\java\com\chengyu\utils\MybatisUtils.java

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static{
        try{
            // 1.获取sqlSesionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    // 获取 SqlSession 实例,可以执行已经映射的sql,类似于statement
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

1.6 补充

Mybatis 核心配置文件中对mapper 的加载路径是target/class 下,默认src 下的配置文件是不被加载的,需要添加配置信息,使得映射文件(AccountMapper.xml)一同加载到编译文件中。

项目的 pom.xml(不是Module)中添加如下配置,并右键Maven ⇒ Reimport 一下。

	<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

重新编译后会发现,target/class 的对应路径下 AccountMapper.xml 文件被加载了。

1.7 测试

public class AccountMapperTest {
    @Test
    public void test(){
        // 获得 SqlSession
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 执行 Sql
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        List<Account> accountList = mapper.getAccountList();
        accountList.forEach(System.out::println);
        sqlSession.close();
    }
}

2. CRUD

2.1 查询

2.2.1 查询多条记录

接口

public interface AccountMapper {
    List<Account> getAccountList();
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="com.chengyu.pojo.Account">
        select * from book.account
    </select>
</mapper>

测试

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        List<Account> accountList = mapper.getAccountList();
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

2.2.2 查询一条记录

接口

public interface AccountMapper {
    Account getAccountById(int id);
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountById" parameterType="int" resultType="com.chengyu.pojo.Account">
        select * from book.account where id = #{id}
    </select>
</mapper>

测试

    @Test
    public void testAccountById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.getAccountById(1001);
        System.out.println(account);
        sqlSession.close();
    }

2.2.3 模糊查询

接口

public interface AccountMapper {
    List<Account> getAccountListLike(String value);
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountListLike" resultType="com.chengyu.pojo.Account">
        select * from book.account where name like "%"#{value}"%"
    </select>
</mapper>

测试

    @Test
    public void testGetAccountListLike(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        List<Account> countList = mapper.getAccountListLike("cheng");
        countList.forEach(System.out::println);
        sqlSession.close();
    }

补充:通配符【%】也可以写在Java 代码中,mapper.getAccountListLike("%cheng%");

2.2 插入

2.2.1 实体类作为插入参数

接口

public interface AccountMapper {
    int addAccount(Account acc);
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <insert id="addAccount" parameterType="com.chengyu.pojo.Account">
        insert into book.account (id,name,balance) value (#{id},#{name},#{balance})
    </insert>
</mapper>

测试

    @Test
    public void testAddAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.addAccount(new Account("chengyu", new BigDecimal("500")));
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

2.2.2 Map 作为插入参数

接口

public interface AccountMapper {
    int addAccount2(Map<String,Object> map);
}

映射:
#{a} 中的名称不必必须遵循实体类的属性,任意。

<mapper namespace="com.chengyu.dao.AccountMapper">
    <insert id="addAccount2" parameterType="map">
        insert into book.account (name) value (#{a})
    </insert>
</mapper>

测试

    @Test
    public void testAddAccount2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("a","chengcheng");
        int count = mapper.addAccount2(map);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

2.3 更新

2.3.1 实体类作为更新参数

接口

public interface AccountMapper {
    int updateAccount(Account acc);
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
	<update id="updateAccount" parameterType="com.chengyu.pojo.Account">
        update book.account set name = #{name},balance = #{balance} where id = #{id}
    </update>
</mapper>

测试

    @Test
    public void testUpdateAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.updateAccount(new Account(1013,"chenglong",new BigDecimal(1500)));
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

2.3.2 Map 作为更新参数

接口

public interface AccountMapper {
    int updateAccount2(Map<String,Object> map);
}

映射
#{b} 中的名称不必必须遵循实体类的属性,任意

<mapper namespace="com.chengyu.dao.AccountMapper">
	<update id="updateAccount2" parameterType="map">
        update book.account set name = #{b} where id = #{id}
    </update>
</mapper>

测试

    @Test
    public void testUpdateAccount2(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("id",1017);
        map.put("b","chengcheng");
        int count = mapper.updateAccount2(map);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

2.4 删除

接口

public interface AccountMapper {
    int deleteAccount(int id);
}

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
	 <delete id="deleteAccount" parameterType="int">
        delete from book.account where id = #{id}
    </delete>
</mapper>

测试

    @Test
    public void testDeleteAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.deleteAccount(1013);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

2.5 补充(占位符)

#{} 和 ${}
都可以作为占位符使用,区别如同PreparedStatement 和Statement。
即 ${} 可能存在SQL 注入问题,不推荐使用。

3. 配置解析

3.1 默认事物配置

Mybatis 默认的事物管理器是JDBC,默认使用连接池(POOLED)。

3.2 数据库连接优化

优化前
mybatis-config.xml

<!-- 核心配置文件 -->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/book?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="tiger"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

优化后
新建resources/db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/book?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8
username=root
password=tiger

resources/mybatis-config.xml

<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.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>
</configuration>

3.3 实体类别名

优化前
AccountMapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="com.chengyu.pojo.Account">
        select * from book.account
    </select>
</mapper>

3.3.1 指定实体类别名设置

优化后
resources/mybatis-config.xml

<configuration>
    <typeAliases>
        <typeAlias type="com.chengyu.pojo.Account" alias="Account"/>
    </typeAliases>
</configuration>

AccountMapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="Account">
        select * from book.account
    </select>
</mapper>

3.3.2 扫描包

包下的实体类,默认首字母小写,即可当作别名使用。可以通过注解起别名。(如3.3.3)
优化后
resources/mybatis-config.xml

<configuration>
    <typeAliases>
        <package name="com.chengyu.pojo"/>
    </typeAliases>
</configuration>

AccountMapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="account">
        select * from book.account
    </select>
</mapper>

3.3.3 注解别名

注解别名需要与扫描包一同使用。
优化后

@Alias("acc")
public class Account {
    private int id;
    private String name;
    private BigDecimal balance;
    ...
}

AccountMapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" resultType="acc">
        select * from book.account
    </select>
</mapper>

3.4 设定信息

settings 中有大量设置信息,如日志、驼峰映射等。
mybatis-config.xml

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

4. 作用域

MybatisUtils:

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static{
        try{
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

4.1 SqlSessionFactoryBuilder

作用域:局部变量

用于创建会话工厂 SqlSessionFactory,不需要使用单例管理。
一旦创建了会话工厂之后,就不再需要它了。

4.2 SqlSessionFactory

作用域:全局作用域

用于创建 SqlSession,使用单例模式管理SqlSessionFactory 。
工厂一旦创建,使用一个实例。

相当于数据库连接池。

4.3 SqlSession

作用域:局部变量

是一个面向用户(程序员)的接口,提供了很多操作数据库的方法。如selectOne、selectList等;
线程不安全的,应该应用在方法体,定义成局部变量使用;
用完之后需要关闭,否则资源被占用。

    @Test
    public void testAccountById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.getAccountById(1001);
        System.out.println(account);
        sqlSession.close();
    }

4.4 AccountMapper

作用域:mapper.xml

每一个Mapper 就代表一个具体的业务。

5. 日志

如果SQL 执行出现异常,可以通过日志进行排错。

有效值:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

5.1 STDOUT_LOGGING

核心配置文件:

<configuration>
    <properties resource="db.properties"/>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    ...
</configuration>

5.2 LOG4J

核心配置文件:

<configuration>
    <properties resource="db.properties"/>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    ...
</configuration>

导包:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

配置文件:
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/chengyu.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

输出:

[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==>  Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<==      Total: 1
Account{id=1001, name='null', balance=700.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.

类中自定义输出内容:

public class AccountMapperTest {
    static Logger logger = Logger.getLogger(AccountMapperTest.class);
    @Test
    public void test(){
        logger.info("====start===");
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        List<Account> accountList = mapper.getAccountList();
        accountList.forEach(System.out::println);
        sqlSession.close();
        logger.info("====end===");
    }
}

6. 分页

避免一次性加载大量数据,影响用户体验度。

6.1 limit 分页

接口

public interface AccountMapper {
    List<Account> getAccountListLimit(Map<String,Integer> map);
    }

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountListLimit" parameterType="map" resultType="Account">
        select id,name as username,balance from book.account limit #{startIndex},#{endIndex}
    </select>
</mapper>

测试

    @Test
    public void testGetAccountListLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        
        Map<String,Integer> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("endIndex",2);
        
        List<Account> accountList = mapper.getAccountListLimit(map);
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

6.2 RowBounds 分页

(不推荐)
接口

public interface AccountMapper {
    List<Account> getAccountListRowBounds();
    }

映射

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountListRowBounds" resultType="Account">
        select id,name as username,balance from book.account
    </select>
</mapper>

测试

    @Test
    public void testGetAccountListRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        RowBounds rowBounds = new RowBounds(0, 2);
        List<Account> accountList = sqlSession.selectList("com.chengyu.dao.AccountMapper.getAccountListRowBounds",null,rowBounds);
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

6.3 分页插件

MyBatis 分页插件 PageHelper
按照操作步骤即可添加使用。

7. 注解开发

简单的Sql 语句可以使用注解形式编写,但复杂语句建议使用XML 的形式。
本质:反射机制
底层:动态代理

工具类:
openSession(true) 设置为自动提交。

public class MybatisUtils {
	...
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
}

核心配置文件:
1)扫描AccountMapper 接口,可以删除Mapper.xml 文件。

    <mappers>
        <mapper class="com.chengyu.dao.AccountMapper"/>
    </mappers>

2)原始的扫描Mapper.xml 文件,同样没问题。
因为在AccountMapper.xml 中引入了AccountMapper 接口,所以扫描到Mapper.xml 也可以使用注解。

    <mappers>
        <mapper resource="com/chengyu/dao/AccountMapper.xml"/>
    </mappers>

7.1 查询多条

接口

public interface AccountMapper {
    @Select("select * from account")
    List<Account> getAccountList();
    }

测试

    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        List<Account> accountList = mapper.getAccountList();
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

7.2 查询一条

接口

public interface AccountMapper {
    @Select("select * from account where id = #{id}")
    Account getAccountById(@Param("id") int id);
    }

测试

    @Test
    public void testAccountById(){
        // 获得 SqlSession
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // 执行 Sql
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        Account account = mapper.getAccountById(1001);
        System.out.println(account);
        sqlSession.close();
    }

7.3 添加

接口

public interface AccountMapper {
    @Insert("insert into account (name,balance) values (#{name},#{balance})")
    int addAccount(Account acc);
    }

测试

    @Test
    public void testAddAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.addAccount(new Account("chengyu", new BigDecimal("500")));
        System.out.println(count);
        sqlSession.close();
    }

7.4 更新

接口

public interface AccountMapper {
    @Update("update account set name=#{name},balance=#{balance} where id=#{id}")
    int updateAccount(Account acc);
    }

测试

    @Test
    public void testUpdateAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.updateAccount(new Account(1020,"chenglong",new BigDecimal(1500)));
        System.out.println(count);
        sqlSession.close();
    }

7.5 删除

接口

public interface AccountMapper {
    @Delete("delete from account where id=#{id}")
    int deleteAccount(@Param("id") int id);
    }

测试

    @Test
    public void testDeleteAccount(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        int count = mapper.deleteAccount(1021);
        System.out.println(count);
        sqlSession.close();
    }

7.6 补充(@Param)

@Param("")
基本类型或String 类型的参数需要使用;
如果只有一个时可以忽略;
SQL 中引用过的就是这里设置的属性名;

8. 高级映射

8.1 一对一 resultType

    <select id="getAccountList" resultType="com.chengyu.pojo.Account">
        select id,name,balance from book.account
    </select>

8.2 一对一 resultMap

解决实体类属性名数据库字段名不一致的问题。查询该字段返回null。

DB   :name
POJO : username
    <!--结果集映射-->
    <resultMap id="AccMap" type="com.chengyu.pojo.Account">
        <result column="name" property="username"/>
    </resultMap>
    <select id="getAccountList" resultMap="AccMap">
        select id,name,balance from book.account
    </select>

补充:Sql 中对字段重命名通用可以解决该问题。

8.3 多对一 resultMap

一个老师对应多个学生,学生实体类中设置老师类属性。

8.3.1 查询嵌套处理

<mapper namespace="com.chengyu.dao.StudentMapper">
    <select id="getStudentList" resultMap="StudentTeacherMap">
        select * from student s,teacher t where s.tid = t.id;
    </select>
   
    <resultMap id="StudentTeacherMap" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂属性 对象:association,集合:collection-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacherList"/>
    </resultMap>
    
    <select id="getTeacherList" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>
</mapper>

8.3.2 结果嵌套处理

<mapper namespace="com.chengyu.dao.StudentMapper">
    <select id="getStudentList2" resultMap="StudentTeacherMap2">
        select s.id,s.name,t.name tname from student s,teacher t where s.tid = t.id;
    </select>
    <resultMap id="StudentTeacherMap2" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂属性 对象:association,集合:collection-->
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
    ...
    }

8.4 一对多 resultMap

一个老师对应多个学生,老师实体类中设置List<学生类实体>。

<mapper namespace="com.chengyu.dao.TeacherMapper">
    <select id="getStudentListById" resultMap="TeacherStudent">
        select t.id,t.name,s.id sid,s.name sname,s.tid
        from teacher t,student s
        where t.id = s.tid and t.id = #{id}
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--集合中的泛型,使用ofType-->
        <!--studentList:Teacher 实体类中的Student 变量-->
        <collection property="studentList" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
</mapper>
public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
    ...
    }

9. 动态 SQL

根据条件对SQL 语句进行灵活的拼接和组装。

9.1 IF

接口

public interface AccountMapper {
    List<Account> getAccountList(Map map);
}

mapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" parameterType="map" resultType="Account">
        select * from account
        <where>
            <if test="name != null">
                and name = #{name}
            </if>
            <if test="balance != null">
                and balance = #{balance}
            </if>
        </where>
    </select>
</mapper>

补充:where 标签自动去除条件中的第一个 and。

测试类

    @Test
    public void getAccountListTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);

        Map map = new HashMap();
        map.put("name","chengyu");
        //map.put("balance",new BigDecimal("1500"));

        List<Account> accountList = mapper.getAccountList(map);
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

9.2 Choose /When /Otherwise

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountList" parameterType="map" resultType="Account">
        select * from account
        <where>
            <choose>
                <when test="name != null">
                    and name = #{name}
                </when>
                <when test="balance != null">
                    and balance = #{balance}
                </when>
                <otherwise>
                    id = 1001
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

9.3 Set

会自动删除无关的逗号。

<mapper namespace="com.chengyu.dao.AccountMapper">
    <update id="updateAccount" parameterType="map">
        update account
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="balance != null">
                balance = #{balance},
            </if>
        </set>
        <where>
            and id = #{id}
        </where>
    </update>
</mapper>

9.4 SQL 片段

共同部分的SQL 语句可以提取出来。

include 部分

<mapper namespace="com.chengyu.dao.AccountMapper">
    <update id="updateAccount" parameterType="map">
        update account
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="balance != null">
                balance = #{balance},
            </if>
        </set>
        <where>
            <include refid="query_account_where"></include>
        </where>
    </update>

    <sql id="query_account_where">
        and id = #{id}
    </sql>
</mapper>

9.5 Foreach

常用与Sql 语句的 in 中。

接口

public interface AccountMapper {
    List<Account> getAccountListByIds(Map map);
}

mapper.xml

<mapper namespace="com.chengyu.dao.AccountMapper">
    <select id="getAccountListByIds" parameterType="map" resultType="Account">
        select * from account
        <where>
            <foreach item="id" collection="ids" open="and (" close=")" separator="or">
                id = #{id}
            </foreach>
        </where>
    </select>
</mapper>

测试

    public void getAccountListByIdsTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);

        Map map = new HashMap();
        List<Integer> list = new ArrayList<>();
        list.add(1001);
        list.add(1002);

        map.put("ids",list);

        List<Account> accountList = mapper.getAccountListByIds(map);
        accountList.forEach(System.out::println);
        sqlSession.close();
    }

Preparing: select * from account WHERE ( id = ? or id = ? )
Parameters: 1001(Integer), 1002(Integer)

10. 缓存

由于连接数据库比较耗资源,将查询结果暂时存放到一个可以直接取到的地方(内存)。这些暂时放到内存中的数据称为缓存。
再次查询相同数据时,就不用走数据库,直接走缓存即可。

经常查询、不经常改变的数据,可以使用缓存。

10.1 一级缓存

默认开启,SqlSession 级别的缓存,也称本地缓存。
在一次连接开始到连接结束之间有效。

原理
第一次发起查询用户Id=1001 的用户信息,先去缓存中找是否有 Id=1001 的用户信息,如果没有,从数据库查询,得到信息后,存入一级缓存中;
第二次发起查询Id=1001 的用户信息,到缓存中找到,则取出用户信息。

查询不同内容,不走缓存;
如果SqlSession 去执行增删改操作,会清空一级缓存,这样做的目的是为了让缓存中的数据最新化;
如果执行两次service 调用查询相同的用户信息,不走一级缓存,因为session 方法结束,SqlSession 就关闭,一级缓存就清空了。

手动清理缓存,sqlSession.clearCache();

    @Test
    public void testAccountById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        
        Account account = mapper.getAccountById(1001);
        System.out.println(account);
		
		System.out.println("================================");

        Account account2 = mapper.getAccountById(1001);
        System.out.println(account2);
        sqlSession.close();
    }

sqlSession 进行了两次查询,因为利用了缓存信息,查询语句只执行一次。

[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==>  Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<==      Total: 1
Account{id=1001, name='null', balance=700.00}
================================
Account{id=1001, name='null', balance=700.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.
    public void testAccountById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);
        
        Account account = mapper.getAccountById(1001);
        System.out.println(account);

        System.out.println("================================");

        Account account2 = mapper.getAccountById(1002);
        System.out.println(account2);
        sqlSession.close();
    }

sqlSession 查询两次,但条件不同,没有缓存信息,查询语句执行了两次。

[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 626742236.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[com.chengyu.dao.AccountMapper.getAccountById]-==>  Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1001(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<==      Total: 1
Account{id=1001, name='null', balance=700.00}
================================
[com.chengyu.dao.AccountMapper.getAccountById]-==>  Preparing: select * from book.account where id = ?
[com.chengyu.dao.AccountMapper.getAccountById]-==> Parameters: 1002(Integer)
[com.chengyu.dao.AccountMapper.getAccountById]-<==      Total: 1
Account{id=1002, name='null', balance=1500.00}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@255b53dc]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 626742236 to pool.

10.2 二级缓存

需要手动缓存,基于namespace 级别的缓存(一个mapper),通过实现Cache 接口来自定义二级缓存。

手动开启方式:
① 核心配置文件

 <settings>
        <setting name="logImpl" value="LOG4J"/>
        <!--显示的开启二级缓存(默认就是开启)-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

② 在mapper.xml 中添加 标记

<mapper namespace="com.chengyu.dao.AccountMapper">
        <cache eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/>
</mapper>

原理:
SqlSession1 去查询用户Id=1001 的用户信息,查询到的用户信息后,先将信息储存到一级缓存中,当SqlSession1 关闭后用户信息存储到二级缓存中(转存);
SqlSession2 去查询 Id=1001 ,先到缓存中查找;

如果同一个mapper.xml 中执行了增删改操作,将会清空该 mapper 下的二级缓存;

注意:
使用二级缓存,需要将实体类序列化!

10.3 自定义缓存

可以自定义,也可以整合第三方缓存框架,如Ehcache。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值