MyBatis学习笔记

1、MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到 Github。

1.1、获取MyBatis

  1. Maven仓库
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
  1. GitHub: https://github.com/mybatis/mybatis-3

  2. 官方中文文档:https://mybatis.org/mybatis-3/zh/index.html

1.2、持久化

  1. 数据持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  2. 内存:断电即失
  3. 持久化方式:数据库(JDBC),IO文件持久化

1.3、持久层

Dao层,service层,controller层

  1. 层是界限明显的
  2. 完成持久化工作的代码块

1.4、MyBatis优点

  1. 简化传统的JDBC代码,实现自动化
  2. 帮助程序员将数据存入数据库
  3. SQL与代码分离
  4. 简单易学,灵活
  5. 提供映射标签,支持对象与数据库ORM映射

2、第一个MyBatis程序

2.1、创建项目

  • 创建数据库
-- 创建数据库
CREATE DATABASE `mybatis`;
USE `mybatis`;

-- 如果存在则删除
DROP TABLE IF EXISTS `user`;

-- 创建数据库
CREATE TABLE `user`(
    `id` INT(20) NOT NULL PRIMARY KEY,
    `name` VARCHAR(30) DEFAULT NULL,
    `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

-- 插入数据
INSERT  INTO `user`(`id`,`name`,`pwd`) VALUES (1,'狂神','123456'),(2,'张三','abcdef'),(3,'李四','987654');`mybatis`
  • 创建普通Maven项目,删除src目录使其充当父工程

  • 导入依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
  • 在父工程中创建一个子模块

2.2、编写核心配置文件

  • 创建mybatis-config.xml配置文件

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/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>
<!--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指定默认环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!--事务管理 默认使用JDBC事务管理-->
            <dataSource type="POOLED">
                <!--配置数据源-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--配置数据库连接 数据库密码-->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

2.3、编写Mybatis工具类

  • 阅读官方文档

SqlSessionFactory中获取到SqlSession,就可以执行其中的SQL语句SqlSession相当于JDBC中的statement

  • 创建utils目录,创建MybatisUtils工具类

package com.liuxiang.utils;

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

public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory=null;

    //从官方文档取出的代码 获取SqlSessionFactory
    static{
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取SqlSession
     *
     * 既然有了 SqlSessionFactory,顾名思义,
     * 我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
     * 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
     * @return
     */
    public  static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}
  • 编写实体类
package com.liuxiang.pojo;

public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  • 编写UserDao接口(与UserMapper一个意思)及其实现类
package com.liuxiang.dao;

import com.liuxiang.pojo.User;

import java.util.List;

public interface UserDao {
    public List<User> getUserList();
}
package com.liuxiang.dao;

import com.liuxiang.pojo.User;

import java.util.List;

public class UserDaoImpl implements UserDao{
    @Override
    public List<User> getUserList() {
        // 编写jdbc代码
        return null;
    }
}

从官方文档可以看出,MyBatis几乎避免了几乎所有JDBC代码,由原来的接口实现类变成xml文件。

所以这里我们不用编写接口实现类,直接创建UserMapper.xml配置文件

  • 创建UserMapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.liuxiang.dao.UserDao">

    <select id="getUserList" resultType="com.liuxiang.pojo.User">
        select * from user
    </select>

</mapper>
<!--UserMapper.xml配置文件解析-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.UserDao"><!--定义接口全路径名-->

    <!--查询语句select id指向接口中的方法名 resultType指向查询结果集封装-->
    <select id="getUserList" resultType="com.liuxiang.pojo.User">
        select * from user
    </select>

</mapper>

2.4、测试程序

  • 编写Test测试文件

编写 Junit 测试文件来测试UserMapper.xml

package com.kuang.dao;

import com.liuxiang.dao.UserDao;
import com.liuxiang.pojo.User;
import com.liuxiang.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
    @Test
    public void testUser(){
        //获取sqlSession
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //指向SQL
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> list = mapper.getUserList();
        for (User user: list) {
            System.out.println(user);
        }
        //关闭SqlSession
        sqlSession.close();
    }
}
  • 排错
  1. 报 org.apache.ibatis.binding.BindingException: Type interface com.liuxiang.dao.UserDao is not known to the MapperRegistry. 错误,原因是核心配置文件中缺少指向UserMapper.xml的配置。

<mappers>
    <mapper resource="com/liuxiang/dao/UserMapper.xml"/>
</mappers>
  1. 报 Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/liuxiang/dao/UserMapper.xml 错误,无法找到 UserMapper.xml 文件,这是Maven过滤的问题的原因 无法导出配置文件。

<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
    <!--设置Maven资源过滤路径-->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
  1. 报 Caused by: org.apache.ibatis.builder.BuilderException: Error creating document instance. Cause: org.xml.sax.SAXParseException; lineNumber: 18; columnNumber: 11; 2 字节的 UTF-8 序列的字节 2 无效的错误,只需要把 mybatis-config.xml 与 UserMapper.xml 文件中的中文注释删除掉,或者修改xml文件中的UTF-8UTF8

  • 测试成功

2.5、作用域与生命周期

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

  • SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

  • SqlSessionFactory

可以把它想象成数据库连接池

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  • SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能被正确地关闭。

  • SqlSession调用方式

2.6、MyBatis执行流程

3、CRUD

3.1、查询

  1. 更改 UserDao 接口名字为 UserMapper ,在UserMapper接口中新增方法
//指定id查询
public User getUser(int id);
  1. UserMapper.xml 中新增SQL映射,其中 parameterType 是入参类型
<select id="getUser" resultType="com.liuxiang.pojo.User" parameterType="int">
    select * from user where id=#{id}
</select>
  1. 编辑测试类
@Test
public void testupdateMapUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();


    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.getUser(2);
    System.out.println(user);

    //关闭SqlSession
    sqlSession.close();
}

3.2、增加

  1. UserMapper 接口中新增方法
//新增用户
public int insertUser(User user);
  1. UserMapper.xml 中新增SQL映射
<insert id="insertUser" parameterType="com.liuxiang.pojo.User" >
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
  1. 编辑测试类
@Test
public void TestinsertUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    int i = mapper.insertUser(new User(4, "狂神", "123456"));
    System.out.println(i);

    //关闭SqlSession
    sqlSession.close();
}
  1. 由于增删改必须要提交事务,所以虽然运行成功了,但是实际数据库并没有新增一行数据,因此再次修改测试代码
//增删改必须要提交事务
@Test
public void TestinsertUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    int i = mapper.insertUser(new User(4, "狂神", "123456"));
    System.out.println(i);
    //提交事务
    sqlSession.commit();
    //关闭SqlSession
    sqlSession.close();
}

3.3、修改

  1. UserMapper 接口中新增方法
//修改用户
public int UpdateUser(User user);
  1. UserMapper.xml 中新增SQL映射
<update id="UpdateUser"  parameterType="com.liuxiang.pojo.User">
    update user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
  1. 编辑测试类
@Test
public void updateUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);


    int i = mapper.UpdateUser(new User(1, "狂神", "123456"));
    System.out.println(i);
    //提交事务
    sqlSession.commit();
    //关闭SqlSession
    sqlSession.close();
}

3.4、删除

  1. UserMapper 接口中新增方法
//删除用户
public int deleteUser(int id);
  1. UserMapper.xml 中新增SQL映射
<delete id="deleteUser" parameterType="int">
    delete from user where id=#{id} ;
</delete>
  1. 编辑测试类
@Test
public void testdeleteUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    int i = mapper.deleteUser(4);
    System.out.println(i);
    //提交事务
    sqlSession.commit();
    //关闭SqlSession
    sqlSession.close();
}

3.5、模糊查询

  1. UserMapper 接口中新增方法
//模糊查询
public List<User> getUserLike();
  1. UserMapper.xml 中新增SQL映射
<select id="getUserLike" resultType="com.liuxiang.pojo.User" parameterType="String">
    select * from user where name like #{value}
</select>
  1. 编辑测试类
@Test
public void getUserLike(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userLike = mapper.getUserLike("%李%");
    for (User user:userLike) {
        System.out.println(user);
    }

    //提交事务
    sqlSession.commit();
    //关闭SqlSession
    sqlSession.close();
}

3.6、Map的使用

在实际工作中,如果实体类或数据库字段过多,使用对象 User 的入参方式过于繁琐且每个字段必须得赋值。此时我们可以使用Map来做参数的传递:

  1. UserMapper 接口中新增map修改方法
//修改用户map
public int updateMapUser(Map<String,Object> map);
  1. UserMapper.xml中新增SQL映射,#{}内的名字可以任意定义,只需要与map中的K保持一致即可
<update id="updateMapUser" parameterType="map">
    update user set name=#{nameMap} where id=#{idMap}
</update>
  1. 编辑测试类
@Test
public void testupdateMapUser(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    Map<String, Object> map = new HashMap<String, Object>();

    map.put("nameMap","刘翔");
    map.put("idMap",2);
    int i = mapper.updateMapUser(map);
    System.out.println(i);

    //提交事务
    sqlSession.commit();
    //关闭SqlSession
    sqlSession.close();
}

map传递参数,直接在SQL中取Key即可,parameterType="map"

对象传递参数,直接取对象的属性即可,parameterType="Object"

如果只有单个基本类型参数,parameterType 可以不写。

多个参数用Map,或者注解!

3.7、Limit实现分页查询

分页查询的作用在于减少数据处理量

  • UserMapper 接口中新增方法
//分页查询
public List<User> limitgetUser(Map<String,Integer> map);
  • UserMapper.xml 中新增SQL映射
<select id="limitgetUser" parameterType="map" resultType="com.liuxiang.pojo.User">
    select * from user limit #{stateInt},#{pageInt}
</select>
  • 编写测试类
@Test
public void testlimitgetUser(){
    //注意导包:org.apache.log4j.Logger
    Logger logger = Logger.getLogger(Log4jTest.class);

    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("stateInt",0);
    map.put("pageInt",2);
    List<User> list = mapper.limitgetUser(map);
    for (User user : list) {
        logger.info("查询结果:"+user);
    }

    //关闭SqlSession
    sqlSession.close();
}

除了使用 Limit 实现分页(SQL),还可以使用 RowBounds 实现分页(Java实现),和分页插件。

3.8、设置自动提交事务

每次都要手动提交事务,相对麻烦
查看SqlSessionFactory源码可知,其中openSession(),可以设置是否自动提交事务。

  • 修改MybatisUtil工具类

4、配置解析

4.1、核心配置文件

  • mybatis-config.xml

4.2、环境配置(environments)

  • MyBatis 可以配置成适应多种环境

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

  • 学会配置多套运行环境

通过default指定默认使用环境

4.3、事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用
  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。
    image-20210717185649614

4.4、数据源(dataSource)

配置数据源来连接数据库

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

  • UNPOOLED– 这个数据源的实现会 每次请求时打开和关闭连接。虽然有点慢, 但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

  • POOLED– 这种数据源的 实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。默认使用

  • JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。

4.5、属性(properties)

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

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

  • 在resources目录下创建db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
  • 在核心配置文件中引入

根据官方文档:标签必须在配置文件最上方,否则会报错

<?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="db.properties"/>


    <!--环境environments 在内配置多套环境 default指定默认环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!--事务管理 默认使用JDBC事务管理-->
            <dataSource type="POOLED"><!--数据源-->
                <!--配置数据驱动-->
                <property name="driver" value="${driver}"/>
                <!--配置数据库连接 数据库密码-->
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>


    <!--每一个Mapper配置文件中都需要在核心配置文件中注册-->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>

</configuration>
  • 也可以在properties标签增加一些其他的属性

如果标签与配置文件都指定了同一个字段,优先使用properties配置文件内的

4.6、设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

常见使用以下配置:

<settings>
    <!--开启缓存-->
    <setting name="cacheEnabled" value="true"/>
    <!--标准的日志工厂STDOUT_LOGGING-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启驼峰命名规则-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

sttings 标签在 properties 标签和 typeAliases 中间位置固定

4.7、类型别名(typeAliases)

  1. 类型别名可为 Java 类型设置一个缩写名字。
  2. 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
  • 方式一(为每个类的全限定类名,取一个特点的名字)

  • 方式二(也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean)

扫描完包,默认会使用 Bean 的首字母小写的非限定类名来作为它的别名,比如 domain.blog.Author 的别名为 author。若有注解,则别名为其注解值。

实体类比较少的时候,建议使用第一种方式。

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

4.8、映射器(mappers)

映射器是告诉 MyBatis 到哪里去找映射文件

定义映射器有以下几种方式:

  1. 相对于类路径的资源引用(这种方式是官网推荐的)

  1. 使用class绑定注册

  1. 扫描指定包下的所有映射文件

使用方式2和方式3需注意1. 接口和他的Mapper配置文件必须同名! 2. 接口和他的Mapper配置文件必须在同一个包下!

5、ResultMap 结果集映射

ResultMap是解决实体类与数据库字段名不一致,导致查询出来的结果无法正确赋值的问题

resultMap 元素是 MyBatis 中最重要最强大的元素

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了

5.1、问题

  • 修改实体类属性,使得其与数据库字段名不一致
private int id;
private String name;
private String password;

  • 执行程序发现字段不匹配的属性无法查出值

5.2、解决方案

  1. 修改SQL语句(使用 as 起别名)
<select id="getUser" resultType="com.liuxiang.pojo.User" parameterType="int">
    select id,name,pwd as password from user where id=#{id}
</select>
  1. 使用resultMap,修改UserMapper.xml
<!--结果集映射-->
<resultMap id="UserMap" type="com.liuxiang.pojo.User">
    <!--column数据库字段 property实体类类名-->
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
    <result column="" property=""/>
</resultMap>

<!-- resultMap -->
<select id="getUser" resultType="com.liuxiang.pojo.User" resultMap="UserMap">
    select * from user where id=#{id}
</select>

如果你已经对 resultMap 足够了解,那么有些实体类与数据库名字相同的字段,完全可以不用显示的定义出来(例如上面的 id 与 name)。

6、日志

6.1、日志工厂

在数据库的使用过程中很多时候我们需要借助日志来进行排错,在以往我们使用:sout,debug。现在Mybatis提供了日志工厂。

SLF4J
LOG4J (掌握)
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING (掌握)
NO_LOGGING

  • 在核心配置文件中加入日志配置
<settings>
    <!--标准的日志工厂STDOUT_LOGGING-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

sttings 标签在 properties 标签和 typeAliases 中间位置固定,否则会报错

6.2、Log4j

  1. Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等

  2. 我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程

  3. 最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  • 导入依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<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/kuang.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
  • 在核心配置文件中配置Log4j日志
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  • log4j简单测试
package dao;

import org.apache.log4j.Logger;
import org.junit.Test;

public class Log4jTest {
    //注意导包:org.apache.log4j.Logger
    static Logger logger = Logger.getLogger(Log4jTest.class);

    @Test
    public void testlog4j(){
        logger.info("普通级别输出日志");
        logger.debug("debug级别输出日志");
        logger.error("紧急级别输出日志");
    }
}

7、使用注解开发

7.1、面向接口编程

大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。

  • 根本原因

解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。

在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了。
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程

  • 关于接口的理解
  1. 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  2. 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类
  1. 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class)

  2. 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface)

  3. 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

  • 三个面向区别
  1. 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法

  2. 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现

  3. 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构

7.2、注解

  • 编写接口
@Select("select * from user")
List<User> getUsers();
  • 在核心配置文件中绑定接口
<mappers>
    <mapper class="com.liuxiang.dao.UserMapper"/>
</mappers>
  • 编写测试类
@Test
public void Test(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();
	//底层主要应用反射
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    List<User> userList = mapper.getUsers();
    for (User user : userList) {
        System.out.println(user);
    }
    //关闭SqlSession
    sqlSession.close();
}

本质:反射机制的实现

底层:动态代理

7.3、注解实现CRUD

  • 编写接口
@Select("select *from user where id=#{id}")
User getUserById(@Param("id") int id);
//有多个参数的时候就必须要加@Param更加规范,是基本参数类型一般还是加上@Param更好
//参数以@Param("以这里为准")
//User getUserById(@Param("id") int id,@Param("name") String name );

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


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

//参数以@Param("以这里为准") int 任意写都可以
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int id);
  • 编写测试类
@Test
public void Test(){
    //获取sqlSession
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //底层主要应用反射
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User userById = mapper.getUserById(2);
    System.out.println(userById);

    //关闭SqlSession
    sqlSession.close();
}

7.4、关于@Param

  1. 基本参数类型或者string类型要加上
  2. 引用类型不需要加
  3. 如果只有一个参数类型的话,可以忽略,但是建议加上
  4. 我们在SQL中引用的就是Param()中设定的属性

7.5、#{} 和 ${} 区别

  1. ${ } 变量的替换阶段是在动态 SQL 解析阶段,而 #{ }变量的替换是在 DBMS 中。
  2. #{}方式能够很大程度防止sql注入。
  3. ${} 方式无法防止Sql注入。${} 方式一般用于传入数据库对象,例如传入表名。
  4. 一般能用#{}的就别用${},所以我们在使用mybatis的时候,尽量的使用 #{} 方式!

8、Lombok

  • 在IDEA中安装 lombok
  1. 点击file点击Settings,找到Plugins插件。

  1. 点击Marketplace,搜索lombok,点击installed即可安装然后重启IDEA即可。

  • 导入 lombok 的jar包
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
  • 在实体类上加注解

@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
  1. @Data 包含无参构造,set,get,hashcode,equlas,tostring

  2. @AllArgsConstructor 有参构造(使用之后@Data包含的无参构造消失,所以如果需要无参构造需要再添加@NoArgsConstructor注解

  3. @Getter and @Setter

  4. @ToString

  5. @EqualsAndHashCode

  • Lombok的优缺点
  1. 优点:

    • 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率

    • 让代码变得简洁,不用过多的去关注相应的方法

    • 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

  2. 缺点:

    • 不支持多种参数构造器的重载
    • 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipse或Intellij IDEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变java语法。它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性。这里我个人最感觉不爽的地方就是对插件的依赖!因为Lombok只是省去了一些人工生成代码的麻烦,但IDE都有快捷键来协助生成getter/setter等方法,也非常方便。

8、多对一处理

  1. 多个学生对应一个导师

  2. 对学生而言关联,多个学生关联一个老师(多对一)

  3. 对老师而言集合,一个老师有很多学生(一对多)

8.1、复杂查询环境搭建

  • 创建数据库表
-- 创建表
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 插入数据
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');

-- 创建表
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 插入数据
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
  • 创建数据库实体类
package com.liuxiang.pojo;

public class Teacher {
    private int id;
    private String name;

    public Teacher() {
    }

    public Teacher(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.liuxiang.pojo;

public class Student {
        private int id;
        private String name;
        //学生关联一个老师
        private Teacher teacher;

        public Student() {
        }

        public Student(int id, String name, Teacher teacher) {
                this.id = id;
                this.name = name;
                this.teacher = teacher;
        }

        public int getId() {
                return id;
        }

        public void setId(int id) {
                this.id = id;
        }

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

        public Teacher getTeacher() {
                return teacher;
        }

        public void setTeacher(Teacher teacher) {
                this.teacher = teacher;
        }

        @Override
        public String toString() {
                return "Student{" +
                        "id=" + id +
                        ", name='" + name + '\'' +
                        ", teacher=" + teacher +
                        '}';
        }
}
  • 创建接口
package com.liuxiang.dao;

public interface TeacherMapper {

}
package com.liuxiang.dao;

public interface StudentMapper {
}
  • 创建Mapper.xml配置文件
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.TeacherMapper"><!--定义接口全路径名-->

</mapper>
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.StudentMapper"><!--定义接口全路径名-->

</mapper>
  • 核心配置文件中添加映射并起别名
<typeAliases>
    <typeAlias type="com.liuxiang.pojo.Teacher" alias="teacher"/>
    <typeAlias type="com.liuxiang.pojo.Student" alias="student"/>
</typeAliases>

<mappers>
    <mapper resource="TeacherMapper.xml"/>
    <mapper resource="StudentMapper.xml"/>
</mappers>

8.2、多对一的处理

需求:查询所有学生以及对应的老师信息

  • 编辑学生接口
// 查询所有的学生信息,以及对应的老师的信息
public List<Student> getStudent();
public List<Student> getStudent2();
  • 编写映射文件
  1. 按照查询嵌套处理–子查询
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.StudentMapper"><!--定义接口全路径名-->

    <resultMap id="studentmap" type="student">
        <!--column数据库字段 property实体类类名-->
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂属性 需要单独处理
        如果字段为对象 选择association
        如果字段为集合 使用collection-->
        <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>

    </resultMap>

    <!--查询所有学生 以及对应的老师信息 resultMap结果集映射-->
    <select id="getStudent" resultMap="studentmap">
        select * from student
    </select>

    <select id="getTeacher" resultType="teacher">
        select * from teacher where id =#{tid}
    </select>
</mapper>
@Test
public void TestStudet (){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> student = mapper.getStudent();
    for (Student stu:student) {
        System.out.println(stu);
    }
    sqlSession.close();
}

  1. 按照结果嵌套处理–联表查询
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.StudentMapper"><!--定义接口全路径名-->

    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname
        from   student s,teacher t
        where s.tid=t.id;
    </select>

    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>

8.3、一对多的处理

  • 修改实体类
package com.liuxiang.pojo;

public class Student {
        private int id;
        private String name;
        //学生关联一个老师
        private int tid;

        public Student() {
        }

        public Student(int id, String name, int tid) {
                this.id = id;
                this.name = name;
                this.tid = tid;
        }

        public int getId() {
                return id;
        }

        public void setId(int id) {
                this.id = id;
        }

        public String getName() {
                return name;
        }

        public void setName(String name) {
                this.name = name;
        }

        public int getTid() {
                return tid;
        }

        public void setTid(int tid) {
                this.tid = tid;
        }

        @Override
        public String toString() {
                return "Student{" +
                        "id=" + id +
                        ", name='" + name + '\'' +
                        ", tid=" + tid +
                        '}';
        }
}
package com.liuxiang.pojo;

import java.util.List;

public class Teacher {
    private int id;
    private String name;

    private List<Student> student;

    public Teacher() {
    }

    public Teacher(int id, String name, List<Student> student) {
        this.id = id;
        this.name = name;
        this.student = student;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Student> getStudent() {
        return student;
    }

    public void setStudent(List<Student> student) {
        this.student = student;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", student=" + student +
                '}';
    }
}
  • 编辑老师接口
//查询指定老师下的所有学生信息及老师信息
public Teacher getTeacher(@Param("tid") int id);
public Teacher getTeacher2(@Param("tid") int id);
  • 编写映射文件
  1. 按照结果嵌套处理–联表查询
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.TeacherMapper"><!--定义接口全路径名-->

    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tnamr,t.id tid
        from student s,teacher t
        where s.tid= t.id and t.id=#{tid};
    </select>

    <resultMap id="TeacherStudent" type="teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="student" ofType="student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
</mapper>
@Test
public void TestStudet (){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    Teacher teacher = mapper.getTeacher(1);
    System.out.println(teacher);
    sqlSession.close();
}
  1. 按照查询嵌套处理–子查询
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace 命名空间 指向 定义一个Dao接口-->
<mapper namespace="com.liuxiang.dao.TeacherMapper"><!--定义接口全路径名-->

    <select id="getTeacher2" resultMap="TeacherStudent2">
        SELECT * FROM teacher WHERE id = #{tid}
    </select>

    <resultMap id="TeacherStudent2" type="teacher">
        <collection property="student" javaType="ArrayList" ofType="student" select="getStudentByTeacherId" column="id"/>
    </resultMap>

    <select id="getStudentByTeacherId" resultType="student">
        select * from student where tid = #{tid}
    </select>
</mapper>

8.4、小结

  1. 关联:association 【多对一】

  2. 集合:collection 【一对多】

  3. javaType & ofType

    • javaType 用来指定实体类中属性的类型
    • ofType 用来指定映射到 List 或者集合中的 pojo 类型,泛型中的约束类型
  4. 注意点:

    • 保证sql的可读性尽量保持通俗易懂
    • 注意一对多和多对一中,属性和字段的问题
    • 如果不好排查建议使用log4j日志

9、动态SQL

什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL

9.1、搭建环境

  • 创建数据库表
-- 创建表
CREATE TABLE `blog` (
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
) ENGINE=INNODB DEFAULT CHARSET=utf8;
  • 创建POJO实体类
package com.liuxiang.pojo;

import lombok.Data;

import java.util.Date;

@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;

}
  • 编写接口
package com.liuxiang.dao;

public interface BlogMapper {
}
  • 编写xml映射文件
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.liuxiang.dao.BlogMapper">
</mapper>
  • 在核心配置文件中引入
<mappers>
    <mapper resource="com/liuxiang/dao/BlogMapper.xml"/>
</mappers>
  • 编写UUID工具类
package com.liuxiang.utils;

import java.util.UUID;

public class IDUtils {
    public static String getId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}
  • 在核心配置文件中设置开启驼峰命名映射
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!--开启驼峰命名规则-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • 增加数据
  1. 编写接口
//插入数据
public int insertBlog(Blog blog);
  1. BlogMapper.xml 中新增SQL映射
<insert id="insertBlog" parameterType="blog">
    insert into blog (id,title,author,create_time,views)
    values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
  1. 编写插入数据程序
import com.liuxiang.dao.BlogMapper;
import com.liuxiang.pojo.Blog;
import com.liuxiang.utils.IDUtils;
import com.liuxiang.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.Date;

public class InsertTest {
    @Test
    public void testaddBlog(){
        SqlSession session = MybatisUtils.getSqlSession();
        BlogMapper mapper = session.getMapper(BlogMapper.class);

        Blog blog = new Blog();
        blog.setId(IDUtils.getId());
        blog.setTitle("Mybatis如此简单");
        blog.setAuthor("狂神说");
        blog.setCreateTime(new Date());
        blog.setViews(9999);

        mapper.insertBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Java如此简单");
        mapper.insertBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("Spring如此简单");
        mapper.insertBlog(blog);

        blog.setId(IDUtils.getId());
        blog.setTitle("微服务如此简单");
        mapper.insertBlog(blog);

        session.close();
    }
}

9.2、if 标签

  • 编写接口
//查询博客
public List<Blog> getBlog(Map map);
  • 编写XML文件
<!--查询-->
<select id="getBlog" parameterType="map" resultType="blog">
    select * from blog where 1=1
    /*如果传入author或title则匹配后面的值*/
    <if test="author != null">and author=#{author} </if>
    <if test="title != null"> and title=#{title}</if>
</select>
  • 编写测试类
@Test
public void testSelectBlog(){
    SqlSession session = MybatisUtils.getSqlSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);

    Map<String, String> map = new HashMap();

    map.put("title","Java如此简单");
    List<Blog> list = mapper.getBlog(map);
    for (Blog blog : list) {
        System.out.println(blog);
    }
    session.close();
}

9.3、trim、where、set标签

  • where标签

在实际生产环境,SQL不能用 where 1=1 这样不安全的查询,可以使用 where 标签匹配后面的查询

<!--查询-->
<!--如果传入author或title则匹配后面的值-->
<select id="getBlog" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="author != null">author=#{author} </if>
        <if test="title != null"> and title=#{title}</if>
    </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • trim标签

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

  • set标签

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

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

9.4、choose (when, otherwise)标签

choose像Java 中的 switch 语句,只会匹配其中一个值

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title=#{title}
            </when>
            <when test="author !=null">
                and author=#{author}
            </when>
            <otherwise>
                views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

9.5、SQL片段

有些时候我们可能将公共(重复) 的SQL抽取出来,方便复用,这就是<SQL>标签。

  • 使用与注意点
  1. 使用<SQL>抽取公共的部分
  2. 在需要使用的地方<include>
  3. 最好基于单表来定义重用的片段
  4. 不要存在<where>标签
  • 修改XML文件
<sql id="if-author-title">
    <if test="author != null">author=#{author} </if>
    <if test="title != null"> and title=#{title}</if>
</sql>

<!--查询-->
<!--如果上面的when都没有匹配成功 则匹配otherwise标签内-->
<select id="getBlog" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-author-title"/>
    </where>
</select>

9.6、foreach标签

  • 编辑接口
//查询用户id为1,2,3的用户
public List<Blog> getBlog(Map map);

  • 编辑XML文件
<!--查询用户id为1,2,3的用户-->
<!--我们现在传入的是一个万能的map,这个map中可以存在一个集合-->
<select id="getBlog" parameterType="map" resultType="blog">
    select * from blog
    <where>
        id in
        <foreach collection="ids"  item="userId" index="index" open="(" separator="," close=")">
            #{userId}
        </foreach>
    </where>
</select>
  • 编写测试类
@Test
public void test2(){
    SqlSession session = MybatisUtils.getSqlSession();
    BlogMapper mapper = session.getMapper(BlogMapper.class);

    Map<String, List> map = new HashMap();

    //准备list集合
    ArrayList<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    ids.add(3);

    map.put("ids",ids);

    List<Blog> blog = mapper.getBlog(map);
    for (Blog blog1 : blog) {
        System.out.println(blog1);
    }
    session.close();
}

动态sql就是在拼接sql语句,我们只要保证sql的正确性,按照sql的格式,去排列组合就好了。

10、缓存

10.1、简介

查询:连接数据库,耗费资源!

一次查询结果,给他暂时存在一个可以直接取到的地方! --> 内存 :缓存

再次查询时就直接从缓存中取到数据就可了

  • 什么是缓存[ Cache ]?
  1. 存在内存中的临时数据。

  2. 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  • 为什么使用缓存?

    减少和数据库的交互次数,减少系统开销,提高系统效率。

  • 什么样的数据能使用缓存?

    经常查询并且不经常改变的数据。

10.2、Mybatis缓存

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

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

  • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)。二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存。

  • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

  • 缓存清除策略

10.3、一级缓存

  1. 一级缓存也叫本地缓存(sqlSession)

  2. 与数据库同一次会话期间查询到的数据会放在本地缓存中

  3. 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库

  • 测试
  1. 创建实体类
package com.liuxiang.pojo;

import lombok.Data;

@Data
public class User {
    private int id;
    private String name;
    private String pwd;
}
  1. 编写接口
package com.liuxiang.dao;

import com.liuxiang.pojo.User;
import org.apache.ibatis.annotations.Param;

public interface UserMapper {
    // 根据id查询用户
    User queryUsers(@Param("id") int id);
}
  1. 编写xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.liuxiang.dao.UserMapper">

    <select id="queryUsers" resultType="user">
        select * from user where id = #{id}
    </select>
</mapper>
  1. 编写测试代码
@Test
public void Test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.queryUsers(1);
    System.out.println(user);

    System.out.println("==========================");
    User user2 = mapper.queryUsers(1);
    System.out.println(user2);

    System.out.println(user==user2);
    
    sqlSession.close();
}

  • 缓存失效的情况
  1. 查询不同的数据
  2. 增删改完要刷新缓存(可能会更改原来的数据,所以会刷新缓存)
  3. 手动清除缓存 sqlSession.clearCache(); // 手动清楚缓存
  4. 查询不同的 Mapper.xml

一级缓存默认开启,只在一次 sqlSession 会话中有效,也就是拿到连接到关闭连接这个区间段。

10.4、二级缓存

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

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

  • 工作机制

    1. 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    2. 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
    3. 新的会话查询信息,就可以从二级缓存中获取内容
    4. 不同的mapper查出的数据会放在自己对应的缓存(map)中
  • 二级缓存是默认关闭的需要手动开启二级缓存

  • 二级缓存生效机制:一级缓存 sqlSession1 关闭的时候会将数据放进二级缓存中,sqlSession2 使用的时候会去二级缓存中取

  • 使用步骤

  1. mybatis-config.xml 中开启全局缓存
<!--开启缓存-->
<setting name="cacheEnabled" value="true"/>
  1. 在要使用二级缓存的 Mapper.xml 映射文件中设置,可以自定义参数
<cache/>
<!--创建了一个 FIFO 缓存 每隔 60 秒刷新 最多可以存储结果对象或列表的 512 个引用 而且返回的对象被认为是只读的-->
<cache
       eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>
  1. Mapper.xml 映射文件中设置可以为不需要使用缓存的方法单独设定

  1. 排错

报错:Cause: java.io.NotSerializableException: com.liuxiang.pojo.User

解决方法:我们需要将实体类序列化,即实现 Serializable 接口。

@Data
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;
}
  1. 测试
@Test
public void Test(){
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = mapper1.queryUsers(1);
    System.out.println(user1);
    sqlSession1.close();

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

    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = mapper2.queryUsers(1);
    System.out.println(user2);
    System.out.println(user1==user2);

    sqlSession2.close();
}

  • 小结
  1. 只要开启了二级缓存,在同一个Mapper下都有效
  2. 所有的数据都会放在一级缓存
  3. 只有当会话提交的时间,或者关闭的时候才会提交到二级缓存中

10.5、缓存原理

  • 缓存顺序
  1. 先看二级缓存有没有数据
  2. 再看一级缓存中有没有数据
  3. 最后查询数据库

10.6、自定义缓存-ehCache

  1. Ehcache 是一种广泛使用的 java分布式缓存,用于通用缓存

  2. 要在应用程序中使用Ehcache,需要引入依赖的jar包

  • 导入Maven依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>
  • mapper.xml 中使用对应的缓存
<!--引入自定义缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • 创建 ehcache.xml 配置文件,里面提供一些自定义配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
</ehcache>
posted @ 2021-07-19 17:05  有一个大佬梦  阅读(57)  评论(0)    收藏  举报