Java框架-MyBatis 详细介绍(crud+缓存+联表+缓存+日志...)

Java框架-MyBatis 详细介绍(crud+缓存+联表+缓存+#{}与¥{}+日志…)

一、MyBatis的介绍

1.1 回顾一下JDBC

下面这个代码是使用JDBC实现基于id查询员工信息,我们来分析分析有什么弊端。

public Employee selectById(Long id) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        conn = JDBCUtils.getConnection();
        ps = conn.prepareStatement("select * from employee where id = ?");
        ps.setLong(1,id);
        rs = ps.executeQuery();
        if (rs.next()){
            Employee employee = new Employee();
            employee.setId(rs.getLong("id"));
            employee.setName(rs.getString("name"));
            employee.setAge(rs.getInt("age"));
            employee.setSex(rs.getInt("sex"));
            employee.setPhone(rs.getString("phone"));
            employee.setAddress(rs.getString("address"));
            employee.setCreatedate(rs.getTimestamp("createdate"));
            return employee;
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCUtils.close(conn,ps,rs);
    }
    return null;
}

弊端如下:
1、SQL代码耦合在Java代码中。
2、SQL中的参数需要自己手动设置,获得结果集后需要自己进行结果的封装。
3、每次需要自己获取连接,用完以后关闭连接。

而这些问题,都可以使用mybatis框架来解决。

2.1 MyBatis的介绍

MyBatis官网英文版:https://mybatis.org/mybatis-3
MyBatis官网中文版:https://mybatis.org/mybatis-3/zh/index.html
MyBatis整合Spring:http://mybatis.org/spring/zh/index.html

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

二、MyBatis基础入门

提前准备数据库表
库名:mytest
表名:user

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `password` varchar(100) DEFAULT NULL,
  `username` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

2.1 导入依赖

<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatisartifactId>
    <version>3.5.6version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>8.0.21version>
dependency>

2.2 创建SqlSessionFactory

咱们使用从 XML 中构建 SqlSessionFactory对象的方式。
① 准备配置文件:在resources目录中新建mybatis核心配置文件mybatis-config.xml,内容如下:


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <environments default="development">
        
        <environment id="development">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mytest?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            dataSource>
        environment>
    environments>
configuration>

② 读取配置文件,创建SqlSessionFactory对象

//1.读取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建Sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

③ 还可以将数据库连接参数(driver,url,username,password)抽离出去,职责单一化。

resources目录中新建db.propertis内容如下:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mytest?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=123456

mybatis-config.xml内容修改如下:


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <properties resource="db.properties">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>

2.3 获取SqlSession并执行SQL

① 创建User实体类

package cn.mybatis.domain;
public class User {
    private Integer id;
    private String password;
    private String username;
    //省略 get  set  toString
}

② 编写映射文件Mapper.xml,将SQL语句抽离到映射文件中。
resources目录新建mapper目录,在其中创建UserMapper.xml
即:resources/mapper/UserMapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.mybatis.mapper.UserMapper">
    
    <select id="selectById" parameterType="int" resultType="cn.mybatis.domain.User">
        select * from user where id = #{id}
    select>
mapper>


③ 加载UserMapper.xml文件
mybatis-config.xml 中添加配置,加载mapper映射文件

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

④ 使用SqlSession执行sql

//1.读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//3.创建sql会话对象
SqlSession session = sqlSessionFactory.openSession();
//4.执行sql
// 参数1:定位sql,命名空间.id
// 参数2:执行sql时的参数
User user = session.selectOne("cn.mybatis.mapper.UserMapper.selectById", 1);
System.out.println(user);
//5.关闭会话
session.close();

三、MyBatis实现CRUD

3.1 新增User

① 映射文件编写sql


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.UserMapper">
    <insert id="insert" parameterType="cn.mybatis.pojo.User">
        insert into user(username,password) values(#{username},#{password})
    insert>
mapper>

② 执行sql

//1.读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//3.创建sql会话对象
SqlSession session = sqlSessionFactory.openSession();
//4.执行sql
User user = new User();
user.setUsername("张三");
user.setPassword("123456");
// 参数1:定位sql,命名空间.id
// 参数2:执行sql时的参数
session.insert("cn.mybatis.mapper.UserMapper.insert",user);
//5.提交事务(mybatis需要手动提交事务)
session.commit();
//6.关闭会话
session.close();

3.2 修改User

① 映射文件编写sql


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.UserMapper">
    <update id="update" parameterType="cn.mybatis.domain.User">
        update user set username=#{username},password=${password} where id = #{id}
    update>
mapper>


② 执行sql

//1.读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//3.创建sql会话对象
SqlSession session = sqlSessionFactory.openSession();
//4.执行sql
User user = new User();
user.setId(1);
user.setUsername("张三");
user.setPassword("123456");
// 参数1:定位sql,命名空间.id
// 参数2:执行sql时的参数
session.update("cn.mybatis.mapper.UserMapper.update",user);
//5.提交事务(mybatis需要手动提交事务)
session.commit();
//6.关闭会话
session.close();

3.3 删除User

① 映射文件编写sql


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.UserMapper">
    <delete id="delete" parameterType="int">
        delete from user where id = #{id}
    delete>
mapper>

② 执行sql

//1.读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//3.创建sql会话对象
SqlSession session = sqlSessionFactory.openSession();
//4.执行sql
// 参数1:定位sql,命名空间.id
// 参数2:执行sql时的参数
session.delete("cn.mybatis.mapper.UserMapper.delete",1);
//5.提交事务(mybatis需要手动提交事务)
session.commit();
//6.关闭会话
session.close();

3.4 查询全部User

① 映射文件编写sql


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.UserMapper">
	
    <select id="selectAll" resultType="cn.mybatis.domain.User">
        select * from user
    select>
mapper>

② 执行sql

//1.读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建sql会话工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
//3.创建sql会话对象
SqlSession session = sqlSessionFactory.openSession();
//4.执行sql
// 参数1:定位sql,命名空间.id
List<Object> userList = session.selectList("cn.mybatis.mapper.UserMapper.selectAll");
System.out.println(userList);
//5.关闭会话
session.close();

经过以上的CRUD练习,我们可以体会到,在mybatis中sql参数的设置和结果集的封装都不需要我们手动完成,简化了编码步骤,但是,可以看出,咱们代码量貌似并没有随之减少。 不用担心,mybatis提供了映射器的方式,可以简化代码。

四、MyBatis映射器

4.1 介绍

MyBatis映射器允许我们只为其提供接口以及相关抽象方法,还有Mapper映射文件,MyBatis就能够使用代理模式为我们进行具体的实现,从而完成数据库操作。

4.2 实现CRUD

4.2.1 提供接口

接口命名规则:XxxxMapper

package cn.mybatis.mapper;
...
public interface UserMapper {
    //增
    void insert(User user);
    //删
    void delete(Integer id);
    //改
    void update(User user);
    //基于id查询
    User selectById(Integer id);
    //查询全部
    List<User> selectAll();
}

4.2.2 提供mapper映射文件

映射文件mapper/UserMapper.xml,核心配置文件mybatis-config.xml不用修改


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.mybatis.mapper.UserMapper">
    
    
    <insert id="insert" parameterType="cn.mybatis.domain.User">
        insert into user(username,password) values(#{username},#{password})
    insert>
    
    <delete id="delete" parameterType="int">
        delete from user where id = #{id}
    delete>
    
    <update id="update" parameterType="cn.mybatis.domain.User">
        update user set username=#{username},password=${password} where id = #{id}
    update>
    
    <select id="selectById" parameterType="int" resultType="cn.mybatis.pojo.User">
        select * from user where id = #{id}
    select>
    
    <select id="selectAll" resultType="cn.mybatis.domain.User">
        select * from user
    select>
mapper>

4.2.3 测试

public class MyBatisTest1 {
    @Test //新增
    public void t1() throws IOException {
        //1.读取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sql会话工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //3.创建sql会话对象
        SqlSession session = sqlSessionFactory.openSession();
        //4.获取映射器(mapper接口)实现类对象(代理模式实现)
        UserMapper mapper = session.getMapper(UserMapper.class);
        //5.执行sql
        User user = new User();
        user.setUsername("张三");
        user.setPassword("123456");
        mapper.insert(user);
        //5.提交事务(mybatis需要手动提交事务)
        session.commit();
        //5.关闭会话
        session.close();
    }

    @Test //删除
    public void t2() throws IOException {
        //1.读取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sql会话工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //3.创建sql会话对象
        SqlSession session = sqlSessionFactory.openSession();
        //4.获取映射器(mapper接口)实现类对象(代理模式实现)
        UserMapper mapper = session.getMapper(UserMapper.class);
        //5.执行sql
        mapper.delete(11);
        //6.提交事务(mybatis需要手动提交事务)
        session.commit();
        //7.关闭会话
        session.close();
    }

    @Test //修改
    public void t3() throws IOException {
        //1.读取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sql会话工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //3.创建sql会话对象
        SqlSession session = sqlSessionFactory.openSession();
        //4.获取映射器(mapper接口)实现类对象(代理模式实现)
        UserMapper mapper = session.getMapper(UserMapper.class);
        //5.执行sql
        User user = new User();
        user.setId(12);
        user.setUsername("李四");
        user.setPassword("123456");
        mapper.update(user);
        //6.提交事务(mybatis需要手动提交事务)
        session.commit();
        //7.关闭会话
        session.close();
    }

    @Test //基于id查询
    public void t4() throws IOException {
        //1.读取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sql会话工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //3.创建sql会话对象
        SqlSession session = sqlSessionFactory.openSession();
        //4.获取映射器(mapper接口)实现类对象(代理模式实现)
        UserMapper mapper = session.getMapper(UserMapper.class);
        //5.执行查询
        User user = mapper.selectById(12);
        System.out.println(user);
        //6.关闭会话
        session.close();
    }
    @Test //查询全部
    public void t5() throws IOException {
        //1.读取mybatis配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sql会话工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        //3.创建sql会话对象
        SqlSession session = sqlSessionFactory.openSession();
        //4.获取映射器(mapper接口)实现类对象(代理模式实现)
        UserMapper mapper = session.getMapper(UserMapper.class);
        //5.执行查询
        List<User> users = mapper.selectAll();
        System.out.println(users);
        //6.关闭会话
        session.close();
    }
}

五、注解方式

对于UserMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。

public interface UserMapper { 
    //基于id查询
    @Select("select * from user where id = #{id}")
    User selectById(Integer id);
	...
}

六、类型别名

类型别名(参数类型或返回值类型)可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

6.1 内置别名

Java框架-MyBatis 详细介绍(crud+缓存+联表+缓存+日志...)_第1张图片

6.2 设置自定义别名

在核心配置文件mybatis-config.xml中增加自定义别名配置。


<typeAliases>
    
    
    
    <package name="cn.mybatis.domain"/>
typeAliases>


② 执行新增,并获取id

InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User();
user.setUsername("tom");
user.setPassword("123456");
//---------新增前 没有id---------
System.out.println("新增前:"+user);
mapper.insert(user);
session.commit();
//---------新增后 有id---------
System.out.println("新增后:"+user);
session.close();

八、打印日志

Mybatis 通过使用内置的日志工厂提供日志功能。支持如下日志方式:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j (3.5.9 起废弃)
  • JDK logging

咱们使用 Log4j 的方式打印mybatis的日志:
① pom.xml 中添加依赖

<dependency>
  <groupId>log4jgroupId>
  <artifactId>log4jartifactId>
  <version>1.2.17version>
dependency>

② 在resources目录下新增配置文件:log4j.properties 名字必须一致

#设置日志级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG、TRACE
log4j.rootLogger=DEBUG, myLog

#设置对谁打印日志
## 表示对cn.mybatis.mapper.UserMapper打印日志
log4j.logger.cn.mybatis.mapper.UserMapper=DEBUG
## log4j.logger.cn.mybatis.mapper=DEBUG   表示对cn.mybatis.mapper包下的所有类都打印日志

#日志打印到控制台
log4j.appender.myLog=org.apache.log4j.ConsoleAppender
#使用自定义布局
log4j.appender.myLog.layout=org.apache.log4j.PatternLayout
#设置输出内容格式
# %d 表示当前系统时间
# %t 执行该业务的线程名
# %p 日志级别, 5表示输出字符的个数
# %c  表示指定业务所在的类的完全限定名(包名.类名)
# %m -- 输出额外信息,%n -- 表示换行
log4j.appender.myLog.layout.ConversionPattern=%d [%t] %5p [%c] - %m%n

九、#{}和${}的区别

默认情况下,mybatis使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement参数占位符,并通过占位符安全地设置参数(就像JDBC中使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以使用 ${}作为占位符,例如:ORDER BY ${columnName},此时,MyBatis 会通过创建 Statement对象来完成。

咱们通过日志打印,来直观的看看 #{} 和 ${} 的区别。
① 使用 #{}

<insert id="insert" parameterType="cn.mybatis.domain.User">
   insert into user(username,password) values(#{username},#{password})
insert>

总结:#{}占位符其底层使用PreparedStatement?号,能够防止sql注入,安全。

② 使用 ${}

<insert id="insert" parameterType="cn.mybatis.domain.User">
   insert into user(username,password) values(${username},${password})
insert>

日志如下,会发现报错了,原因是直接替换参数,缺少了引号。

[cn.mybatis.mapper.UserMapper.insert] - ==>  Preparing: insert into user(username,password) values(root,123456)
[cn.mybatis.mapper.UserMapper.insert] - ==> Parameters: 
org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column 'root' in 'field list'

想要插入成功,就需要自己根据数据类型来加引号。

<insert id="insert" parameterType="cn.mybatis.domain.User">
	insert into user(username,password) values('${username}','${password}')
insert>

总结:${}占位符本质就是字符串拼接,使用Statement完成,会有sql注入的风险,不安全。但是在一些特殊情况下,例如:ORDER BY ${字段名} 或者 GROUP BY ${字段名}等场景就可以使用${}占位符。

十、动态SQL

准备数据

CREATE TABLE `blog` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL,
  `author` varchar(255) DEFAULT NULL,
  `content` varchar(255) DEFAULT NULL,
  `state` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

INSERT INTO `blog` VALUES (1, '学Java', 'msk', '学学学学学学学学', 'active');
INSERT INTO `blog` VALUES (2, '学php', '张三', '习习习习习习', 'active');
INSERT INTO `blog` VALUES (3, '学JS', '王五', '学习学习学习学习学习', 'disable');
INSERT INTO `blog` VALUES (4, '学py', '王五', '入门到放弃', 'disable');

实体类

public class Blog {
    private Long id;
    private String title;
    private String author;
    private String content;
    private String state;
    //省略 get  set  toString
}

10.1 if

BlogMapper接口

public interface BlogMapper {
    /**
     * 根据条件查询博客
     * @param blog 
     * @return
     */
    List<Blog> selectBlog(Blog blog);
}

BlogMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.BlogMapper">
    
    <select id="selectBlog" resultType="Blog">
        select * from blog
        where state = 'active'
        <if test="title!=null and title!=''">
            and title = #{title}
        if>
        <if test="author!=null and author!=''">
            and author = #{author}
        if>
    select>
    
mapper>

在update语句中也适用,来看这样一段伪代码:

<update id="update" parameterType="User">
    update user set username=#{username}
    <if test="password!=null and password!=''">
    	,password=${password}
    if>
    where id = #{id}
update>

10.2 where

if语句能够通过判断实现动态sql,但是如果出现下面场景,可能就会出现问题,看代码:

<select id="selectBlog" resultType="Blog">
    select * from blog
    where
    <if test="state!=null and state!=''">
        state = #{state}
    if>
    <if test="title!=null and title!=''">
        and title = #{title}
    if>
    <if test="author!=null and author!=''">
        and author = #{author}
    if>
select>

如果此时,3个条件都不传,就是出现这样的sql:

select * from blog where

如果只传了title,是这样的sql:

select * from blog where and title =?

为了解决这个问题,mybatis提供了where标签,使用方式如下:

<select id="selectBlog" resultType="Blog">
    select * from blog
    <where>
        <if test="state!=null and state!=''">
            state = #{state}
        if>
        <if test="title!=null and title!=''">
            and title = #{title}
        if>
        <if test="author!=null and author!=''">
            and author = #{author}
        if>
    where>
select>


10.3 foreach

这里咱们使用两个案例来讲解foreach标签。
案例1:批量删除

public interface BlogMapper {
    //批量删除
    void deleteBatch(List<Long> ids);
}

SQL编写:

<!--
    批量删除语法:delete from blog where id in (xx,xx,xx)
    
    collection:被遍历的是谁。
        需要注意:针对list和array,mybatis将他们封装为了map,key分别为:list和array,value就是传入的实参
                所以,collection="list"就是获取list的值,collection="array"就是获取数组的值,而不是collection="ids"
    item:遍历的当前项
    open:开始符号
    separator:分隔符
    close:结束符号
-->
<delete id="deleteBatch">
    delete from blog where id in
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

案例2:批量插入

public interface BlogMapper {
    //批量插入
    void insertBatch(List<Blog> blogs);
}

SQL编写:

<insert id="insertBatch">
    insert into blog(title,author,content,state)
    values
    <foreach collection="list" item="blog" separator=",">
        (#{blog.title},#{blog.author},#{blog.content},#{blog.state})
    foreach>
insert>

10.4 set

以修改为例,咱们来学习一下set元素,set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

public interface BlogMapper {
    //修改博客
    void updateBlog(Blog blog);
}

SQL编写:

<update id="updateBlog">
    update blog
    <set>
        <if test="title!=null">
            title = #{title},
        if>
        <if test="author!=null">
            author = #{author},
        if>
        <if test="content!=null">
            content = #{content},
        if>
        <if test="state!=null">
            state = #{state}
        if>
    set>
    where id = #{id}
update>

10.5 choose、when、otherwise

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

public interface BlogMapper {
    //基于条件查询博客
    List<Blog> findActiveBlog(Blog blog);
}

SQL编写:

<select id="findActiveBlog" resultType="Blog">
    select * from blog WHERE state = 'active'
    <choose>
        <when test="title != null">
            AND title = #{title}
        when>
        <when test="content != null">
            AND content = #{content}
        when>
        <otherwise>
            AND author = 'msk'
        otherwise>
    choose>
select>

10.6 trim

trim 元素可以用来定制自己的SQL。他包含4个属性:prefix - 前缀,prefixOverrides - 前置覆盖,suffix - 后缀,suffixOverrides - 后置覆盖。
案例1:查询中使用trim


<select id="selectBlog" resultType="Blog">
    select * from blog
    <where>
        <if test="state!=null and state!=''">
            state = #{state}
        if>
        <if test="title!=null and title!=''">
            and title = #{title}
        if>
        <if test="author!=null and author!=''">
            and author = #{author}
        if>
    where>
select>


<select id="selectBlog" resultType="Blog">
    select * from blog
    <trim prefix="where" prefixOverrides="and | or">
        <if test="state!=null and state!=''">
            state = #{state}
        if>
        <if test="title!=null and title!=''">
            and title = #{title}
        if>
        <if test="author!=null and author!=''">
            and author = #{author}
        if>
    trim>
select>

案例2:修改中使用trim


<update id="updateBlog">
    update blog
    <set>
        <if test="title!=null">
            title = #{title},
        if>
        <if test="author!=null">
            author = #{author},
        if>
        <if test="content!=null">
            content = #{content},
        if>
        <if test="state!=null">
            state = #{state}
        if>
    set>
    where id = #{id}
update>

<update id="updateBlog">
    update blog
    <trim prefix="set" suffixOverrides=",">
        <if test="title!=null">
            title = #{title},
        if>
        <if test="author!=null">
            author = #{author},
        if>
        <if test="content!=null">
            content = #{content},
        if>
        <if test="state!=null">
            state = #{state}
        if>
    trim>
    where id = #{id}
update>


<update id="updateBlog">
    update blog
    <trim prefix="set" suffixOverrides="," suffix="where id = #{id}">
        <if test="title!=null">
            title = #{title},
        if>
        <if test="author!=null">
            author = #{author},
        if>
        <if test="content!=null">
            content = #{content},
        if>
        <if test="state!=null">
            state = #{state}
        if>
    trim>
update>

10.7 sql

使用sql元素,可以抽取sql片段。


<sql id="columnName">
    id,title,author,content,state
sql>


<select id="selectAll" resultType="Blog">
    select <include refid="columnName"/> from blog
select>

十一、关联查询

准备测试数据

-- 部门表
CREATE TABLE `department` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `department` VALUES (1, '研发部');
INSERT INTO `department` VALUES (2, '产品部');
INSERT INTO `department` VALUES (3, '测试部');

-- 员工表
CREATE TABLE `employee` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `dept_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `employee` VALUES (1, '张三', 2);
INSERT INTO `employee` VALUES (2, '李四', 1);
INSERT INTO `employee` VALUES (3, '王五', 1);
INSERT INTO `employee` VALUES (4, '赵六', 3);

对应实体类

public class Department {
    private Long id;
    private String name;
	//get set toString...
}
public class Employee{
    private Long id;
    private String name;
	//get set toString...
}

11.1 自定义映射ResultMap

咱们以部门表为例来演示自定义映射ResultMap,假设实体类中部门名称不是name,而是myname,进行查询后,会怎么样呢?部门名称会被查询出来吗?

package cn.mybatis.mapper;

public interface DepartmentMapper {
    //查询部门
    List<Department> selectAll();
}

DepartmentMapper.xml


DOCTYPE mapper 
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.DepartmentMapper">
    <select id="selectAll" resultType="Department">
        select * from department
    select>
mapper>

使用自定义映射ResultMap来实现部门名称的查询。


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.mybatis.mapper.DepartmentMapper">
    
    <select id="selectAll" resultMap="departmentMap">
        select * from department
    select>
    
    <resultMap id="departmentMap" type="Department">
        
        <id column="id" property="id">id>
        
        <result column="name" property="myname">result>
    resultMap>
mapper>

11.2 一对多

从部门的角度出发,一个部门可以包含多个员工,即:1对多。
需求:查询出部门信息,以及每个部门下的员工信息。

11.2.1 实现方式1:关联查询

① SQL编写

SELECT d.*,e.id,e.name FROM department d 
LEFT JOIN employee e 
ON d.id = e.dept_id

② 对实体类进行改造

public class Employee {
    private Long id;  //员工id
    private String name; //员工姓名
    // get set toString...
}
public class Department {
    private Long id;   //部门id
    private String name; //部门名称
    private List<Employee> employees;//部门下的员工
    // get set toString...
}

③ mapper接口

public interface DepartmentMapper {
    //查询部门和部门下的员工信息
    List<Department> selectDepartmentAndEmp();
}

④ mapper映射文件:DepartmentMapper.xml

<select id="selectDepartmentAndEmp" resultMap="deptMap">
    SELECT d.*,e.id,e.name FROM department d LEFT JOIN employee e ON d.id = e.dept_id
select>
<resultMap id="deptMap" type="Department">
    <id column="id" property="id">id>
    <result column="name" property="name">result>
    
    <collection property="employees" ofType="Employee">
        
        <id column="id" property="id">id>
        
        <result column="name" property="name">result>
    collection>
resultMap>

会发现这样查的话,因为恰巧部门和员工都有id和name属性,会导致查询结果是:

Department{id=1, name='研发部', employees=[Department{id=1, name='研发部'}]}
Department{id=2, name='产品部', employees=[Department{id=2, name='产品部'}]}
Department{id=3, name='测试部', employees=[Department{id=3, name='测试部'}]}

解决办法是:可以为员工的字段名设置别名。

<select id="selectDepartmentAndEmp" resultMap="deptMap">
    SELECT d.*,e.id eid,e.name ename FROM department d LEFT JOIN employee e ON d.id = e.dept_id
select>
<resultMap id="deptMap" type="Department">
    <id column="id" property="id">id>
    <result column="name" property="name">result>
    
    <collection property="employees" ofType="Employee">
        
        <id column="eid" property="id">id>
        
        <result column="ename" property="name">result>
    collection>
resultMap>

最终查询结果为:

Department{id=1, name='研发部', employees=[Department{id=2, name='李四'}, Department{id=3, name='王五'}]}
Department{id=2, name='产品部', employees=[Department{id=1, name='张三'}]}
Department{id=3, name='测试部', employees=[Department{id=4, name='赵六'}]}

11.2.2 实现方式2:子查询

<select id="selectDepartmentAndEmp" resultMap="deptMap">
    SELECT * FROM department
select>
<resultMap id="deptMap" type="Department">
    <id column="id" property="id">id>
    <result column="name" property="name">result>
    
    <collection property="employees" ofType="Employee" select="selectEmp" column="id">collection>
resultMap>
<select id="selectEmp" resultType="Employee">
    select * from employee where dept_id = #{deptId}
select>

11.3 多对一

从员工的角度出发,多个员工隶属于一个部门,即:多对一。
需求:查询员工信息,以及每个员工所属部门信息。

11.3.1 实现方式1:关联查询

① SQL编写

SELECT e.id,e.name,d.id did,d.name dname FROM employee e 
LEFT JOIN department d 
ON e.dept_id=d.id

② 对实体类进行改造

public class Employee {
    private Long id;     //员工id
    private String name; //员工姓名
    private Department department; //员工所属部门
    // get set toString...
}
public class Department {
    private Long id;   //部门id
    private String name; //部门名称
    //get set toString
}

③ mapper接口

public interface EmployeeMapper {
    //查询员工和对应的部门信息
    List<Employee> selectEmployeeAndDept();
}

④ mapper映射文件:EmployeeMapper.xml

<select id="selectEmployeeAndDept" resultMap="EmployeeMap">
    SELECT e.id,e.name,d.id did,d.name dname FROM employee e
    LEFT JOIN department d
    ON e.dept_id=d.id
select>
<resultMap id="EmployeeMap" type="Employee">
    <id column="id" property="id">id>
    <result column="name" property="name">result>
    
    <association property="department" javaType="Department">
        <id column="did" property="id">id>
        <result column="dname" property="name">result>
    association>
resultMap>

11.3.2 实现方式2:子查询

<select id="selectEmployeeAndDept" resultMap="EmployeeMap">
    SELECT *  FROM employee
select>
<resultMap id="EmployeeMap" type="Employee">
    <id column="id" property="id">id>
    <result column="name" property="name">result>
    
    <association property="department" javaType="Department" select="selectDepartmentById" column="dept_id">association>
resultMap>
<select id="selectDepartmentById" resultType="Department">
    SELECT *  FROM department where id = #{id}
select>

十二、mybatis的缓存

mybatis为了提高查询效率,使用缓存的方式将查询结果缓存到内存中,当相同的查询执行第二次时,并不会真实的执行数据库查询,而是从缓存中获取数据返回。mybatis提供了两种缓存方式。

12.1 mybatis的一级缓存

一级缓存:也称为SqlSession(会话)级别的缓存,用于保存用户在一次会话过程中查询的结果,如果用户使用同一个SqlSession执行了两次相同的查询,第二次则会使用缓存中的数据,而不会执行数据库查询,同时,一级缓存是自动开启的。
Java框架-MyBatis 详细介绍(crud+缓存+联表+缓存+日志...)_第2张图片

@Test
public void testselectByPrimaryKey() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session = sqlSessionFactory.openSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    //第一次查询
    Blog blog1 = mapper.selectByPrimaryKey(4L);
    System.out.println(blog1);
    //第二次查询
    Blog blog2 = mapper.selectByPrimaryKey(4L);
    System.out.println(blog2);
    session.close();
}

从日志从可以看出,仅会执行一次查询:
在这里插入图片描述

但是由于每个SqlSession有自己的缓存,这就会导致出现脏数据的问题。

12.2 mybatis的二级缓存

二级缓存:也称为全局缓存或者跨会话级别的缓存。可以实现不同的SqlSession执行同一个Mapper(namespace命名空间)下的查询SQL时,相同查询第二次就会从缓存中获取返回,而不会真正的执行数据库查询。

Java框架-MyBatis 详细介绍(crud+缓存+联表+缓存+日志...)_第3张图片

默认情况下是没有开启二级缓存的,需要手动开启。
1.在核心配置文件mybatis-config.xml中加入:


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

2.在mapper映射文件中加入:

<mapper namespace="...">
    ...
    <cache/>
    ...
mapper>

3.测试看结果

@Test
public void testselectByPrimaryKey() throws IOException {
    InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
    SqlSession session1 = sqlSessionFactory.openSession();
    BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
    //第一个SqlSession查询
    Blog blog1 = mapper1.selectByPrimaryKey(4L);
    System.out.println(blog1);
    session1.close();

    //第二个SqlSession查询
    SqlSession session2 = sqlSessionFactory.openSession();
    BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
    Blog blog2 = mapper2.selectByPrimaryKey(4L);
    System.out.println(blog2);
    session2.close();
}

在这里插入图片描述

你可能感兴趣的:(mybatis,java,缓存)