MyBatis

MyBatis框架

引入

文章目录


在学习之前,回顾一下JDBC查询操作。

package com.lwg.dao;

import com.lwg.domain.User;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class  userMapper {
    public static void main(String[] args) {
        try {
            //1、加载数据库驱动,底层自动会将驱动注册到驱动管理器上
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2、通过驱动管理器获取连接对象
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/practical_training? useUnicode=true&characterEncoding=utf8", "root", "20010107wdsr");
            //3、设置sql语句
            String sql="SELECT * FROM USER WHERE user_id=?";
            //4、通过连接对象获取执行对象
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            //5、设置占位符
            preparedStatement.setInt(1,2);
            //6、执行sql语句,获取结果集
            ResultSet resultSet = preparedStatement.executeQuery();
            List<User> users=new ArrayList<User>();
            //7、遍历结果集
            while (resultSet.next()){
                int userId = resultSet.getInt("user_id");
                String userName = resultSet.getString("user_name");
                String password = resultSet.getString("password");
                String nature = resultSet.getString("nature");
                User user=new User();
                user.setUserId(userId);
                user.setUserName(userName);
                user.setPassword(password);
                user.setNature(nature);
                users.add(user);
            }
            System.out.println("users = " + users);
            //8、释放资源
            resultSet.close();
            preparedStatement.close();
            connection.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

原生的JDBC有一些问题:

1、每次执行sql语句都要获取一次连接对象,影响数据库性能。

2、sql语句写在代码里,每次修改sql语句都要修改代码。

3、设置占位符麻烦

4、结果集的数据解析和封装麻烦

MyBatis框架介绍

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

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

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

优点:

  1. 通过配置的方式配置数据库连接池, 其实你可以不用管连接池,因为MyBatis内置了连接池,后期也可以通过Spring框架来整合第三方数据库连接池,例如c3p0, druid等。
  2. 通过配置[XML/注解]的方式配置SQL语句。
  3. 框架底层还会自动解析结果集封装到对应的JavaBean中。
  4. MyBatis中支持事务, 但是事务后期会交给Spring容器管理。
  5. 在MyBatis框架中,提供了一个最为核心的功能,框架让开发者不需要编写dao接口的实现类, 底层会通过动态代理创建接口的实现类. 对于开发中来说只需要会编写SQL语句就好了,从而极大程度提高了开发的效率以及代码的可读性,扩展性和维护性。

入门案例

1、导入依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
2、构建 SqlSessionFactory
  • 每个 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
  • SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得
  • SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例
一、通过配置文件构建 SqlSessionFactory
  • 从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

SqlSessionFactory 对象代码如下:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  • XML 配置文件中包含了对 MyBatis 系统的核心设置,这里罗列了一些最关键的的配置

    • 获取数据库连接实例的数据源(DataSource),即数据库连接池。
    • 决定事务作用域控制方式的事务管理器(TransactionManager)。
    • 映射器,这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息

    简单的配置示例

    <?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/practical_training? useUnicode=true&amp;characterEncoding=utf8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="20010107wdsr"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
           <package name="com.lwg.dao"/>
        </mappers>
    </configuration>
    
二、Java代码构建SqlSessionFactory
  • 如果你更愿意直接从 Java 代码而不是 XML 文件中创建配置,或者想要创建你自己的配置建造器,MyBatis 也提供了完整的配置类,提供了所有与 XML 文件等价的配置项。Java代码如下:
 //获取数据库连接池,使用阿里巴巴的druid连接池
        DruidDataSource dataSource = new DruidDataSource();
        //2、设置数据库连接信息
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/practical_training? useUnicode=true&amp;characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("2001017wdsr");
        //3、创建事务管理器
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        //4、设置mybatis环境对象
        Environment environment = new Environment("development", transactionFactory, dataSource);
        //5、设置MyBatis核心配置对象,并添加mybatis环境配置
        Configuration configuration = new Configuration(environment);
        //6、添加MyBatis环境配置
        configuration.addMapper(UserMapper.class);
        //7、构建sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
3、从 SqlSessionFactory 中获取 SqlSession
  • 通过 SqlSessionFactory获得SqlSession 的实例。
  • SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。

通过SqlSession 实例来直接执行已映射的 SQL 语句

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog)  User user = sqlSession.selectOne("com.lwg.dao.UserMapper.slectelOne", 1);
}

使用和指定语句的参数和返回值相匹配的接口

try (SqlSession session = sqlSessionFactory.openSession()) {
  UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.slectelOne(1);
}
SQL映射配置
  • MyBatis支持以下两种方式来配置dao方法和SQL的映射,一种是XML配置,一种是注解配置,我们先看看 XML 定义语句的方式,事实上 MyBatis 提供的所有特性都可以利用基于 XML 的映射语言来实现。这里给出一个基于 XML 映射语句的示例,它应该可以满足上个示例中 SqlSession 的调用。配置如下所示
<?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.lwg.dao.UserMapper">
    <select id="slectelOne" parameterType="int" resultType="com.lwg.domain.User" >
        select * from USER where user_id=#{id}
    </select>
</mapper>
  • 对于像 UserMapper 这样的映射器类来说,还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如,上面的 XML 示例可以被替换成如下的配置:

xFtXG9.png

作用域和生命周期
SqlSessionFactoryBuilder

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

SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));

**注意:**上述方式采用匿名对象的方式创建,SqlSessionFactoryBuilder在创建并且使用完毕以后会自动成为垃圾对象,等待垃圾回收器回收,节约内存资源。

SqlSessionFactory

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

package com.lwg.utils;

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

import javax.print.DocFlavor;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author lwg
 * @create 2022/9/22 15:07
 */
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    private MyBatisUtils(){};
    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
SqlSession

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

try (SqlSession session = sqlSessionFactory.openSession()) { // 你的应用逻辑代码 
}

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

映射器实例

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发
现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。示例代码如下所示:

try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(BlogMapper.class); 
// 你的应用逻辑代码 
}
MyBatis工具类的封装

为了简化MyBatis的工作流程,将一些重复的操作封装成方法。

工具类代码如下:

package com.lwg.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 javax.print.DocFlavor;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;

/**
 * @author lwg
 * @create 2022/9/22 15:07
 */
public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    private MyBatisUtils(){};
    static {
        try {
            //编写MyBatis配置文件的路径
            String resource = "mybatis-config.xml";
            //通过Resources工具类获取配置文件对应的文件流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //通过SqlSessionFactoryBuilder对象获取 sqlSessionFactory工厂对象
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(boolean submit){
        return sqlSessionFactory.openSession(submit);
    }
    public static void closeSqlSession(SqlSession sqlSession){
        if (Objects.nonNull(sqlSession)) {
            sqlSession.close();
        }
    }
}

MyBatis核心流程以及工作原理

MyBatis核心对象
SqlSession对象

该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。

Executor接口

它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓
存的维护。类似于JDBC里面的Statement/PrepareStatement。

MappedStatement对象

该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信
息。

ResultHandler对象

用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自
定义返回类型。

MyBatis工作原理

xF0gaV.png

流程说明:

  1. 读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
  2. 加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
  3. 构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory.
  4. 创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执SQL语句的所有方法。
  5. Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
  6. MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
  7. 输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
  8. 输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

注意:

在MyBatis中,SqlSession对象包含了执行SQL语句的所有方法。但是它是委托Executor执行的。从某种意义上来看,MyBatis里面的SqlSession类似于JDBC中的Connection,它们都是委托给其它类去执行。

SqlSession对象包含了执行SQL语句的所有方法,但是它同样包括了:

T getMapper(Class type);

所以SqlSession也可以委托给映射器来执行数据的增删改查操作。如下代码所示:

xFBddx.png

MyBatis入门程序步骤总结

​ 1、导入依赖

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

​ 2、编写mybatis核心配置文件

<?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是核心配置的根标签,表示配置的意思
-->
<configuration>
    <!--
    environments表示配置mybatis环境,可以有一个也可以有多个
    default表示默认使用哪种环境
    -->
    <environments default="development">
        <!--
        environment表示一种环境,连接不同数据库需要不同的环境
        id为该环境的唯一标识
        -->
        <environment id="development">
            <!--
              transactionManager:配置事务管理器
              type:表示事务的类型
              可取值:
              JDBC:表示支持事务,使用MyBatis默认事务,默认事务就是支持事务,你可以提交事务,可以回滚事务
              MANAGER:表示不支持事务
              第三方事务: 后期如果想要使用事务,都会交给Spring来处理
              -->
          <transactionManager type="JDBC"/>
            <!--
             dataSource:设置数据库连接池
             type:数据库连接池类型
             可取值:
             POOLED:使用MyBatis自带的数据库连接池
             UNPOOLED: 表示不使用数据库连接池,每次获取连接都要创建一个新的连接对象,性能比较低。
             JNDI:表示使用web服务器配置的数据库连接池

             -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/practical_training? useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="20010107wdsr"/>
            </dataSource>
        </environment>
    </environments>
    <!--
    mappers:表示配置映射器,里面可写mapper、package标签
    映射器指的就是 Java接口的方法和SQL的映射关系 的配置
    mapper:表示映射,一个映射表示一张表。
    package:表示映射关系所在的包,将该包下的所以映射都加载进来了
    -->
    <mappers>
       <package name="com.lwg.dao"/>
    </mappers>
</configuration>

3、编写实体类(如User)

4、编写dao接口(如UserMapper)

5、编写映射配置文件(如UserMapper.xml)

6、编写测试类,测试对应方法

注意:

  • dao接口要和映射配置文件路径要相同
  • 编写映射配置文件后要在mybatis核心配置文件里配置映射关系

MyBatis整合Log4j框架

MyBatis框架通过使用内置日志工厂提供日志功能,日志工厂会将日志工作托付给下面之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

MyBatis日志工厂运行时会依次检查选择日志托付实现,第一个找到的就托付给它实现,否则日志功能禁用。

注意:

不少应用服务器(如 Tomcat 和 WebShpere)的类路径中已经包含 Commons Logging。在这种配置环境下,MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中,由于提供了 Commons Logging 的私有实现,你的 Log4J 配置将被忽略。这个时候你就会感觉很郁闷:看起来 MyBatis 将你的 Log4J 配置忽略掉了(其实是因为在这种配置环境下,MyBatis 使用Commons
Logging 作为日志实现)。如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志实现,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting来选择其它日志实现。

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

value:可选的值有SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名。

日志配置(配置LOG4J)

1、导依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2、编写log4j配置文件

在类的路径下创建一个log4j.properties文件,文件代码如下:

# 全局日志配置
log4j.rootLogger=DEBUG, stdout
# MyBatis 日志配置 
log4j.logger.com.lwg.dao.UserMapper=TRACE
# 控制台输出 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

细化级的输出日志,控制台输出com.xyr.dao.UserMapper里的slecteltById方法的日志:

log4j.logger.com.xyr.dao.UserMapper.slecteltById=TRACE

粗矿级的输出日志, 控制台输出com.xyr.dao包下的日志:

log4j.logger.com.xyr.dao=TRACE

3、修改MyBatis配置文件,配置日志

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

4、运行测试类

运行的sql语句就会打印到控制台上。

xkV5Uf.png

MyBatis基于XML配置实现CRUD

添加操作

添加用户记录

UserMapper接口的代码:

public interface UserMapper {
    public int insertUser(User user);
}

UserMapper.xml的代码:

  <insert id="insertUser" parameterType="com.lwg.domain.User" >
        insert into user(user_name,password,nature) values (#{userName},#{password},#{nature})
    </insert>

测试类代码

package com.lwg.dao;




import com.alibaba.druid.support.ibatis.SqlMapClientImplWrapper;
import com.lwg.domain.User;
import com.lwg.utils.MyBatisUtils;
import org.apache.ibatis.annotations.Mapper;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;

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

public class UserDaoTest {
    private SqlSession sqlSession;
    private UserMapper userMapper;
    @Before
    public void getSqlSession(){
       sqlSession= MyBatisUtils.getSqlSession(true);
       userMapper=sqlSession.getMapper(UserMapper.class);
    }
    @After
    public void closeSqlSession(){
        sqlSession.close();
    }
     @Test
    public void insertUserTest(){
        User user=new User();
        user.setUserName("令狐冲");
        user.setPassword("123456");
        user.setNature("-1");
        System.out.println("user = " + user);
        int result = userMapper.insertUser(user);
        System.out.println("result = " + result);
    }
}

注意:

​ 添加属于DML语句,针对DML语句需要进行事务提交,比如更新、删除都要提交事务,事务提交有两种方式:

  • 手动调用sqlSession对象的commit方法进行显示提交。
sqlSession.commit();
  • 在创建sqlSession对象时开启自动提交事务
sqlSessionFactory.openSession(true); //默认为false不自动提交

主键回填

在将数据插入数据库后,会自动将会自动将数据库的数据映射到Java的User对象属性中,但是id的值不会映射到Java对象的id属性中。使得id的值映射到Java对象的id属性中即为主键回填。两种方式实现:

  • 方式一 使用 useGeneratedKeys 属性
<insert id="insertUser" parameterType="com.lwg.domain.User" useGeneratedKeys="true" keyProperty="userId" >
    insert into user(user_name,password,nature) values (#{userName},#{password},#{nature})
</insert>

useGeneratedKeys:表示开启主键回填。

keyProperty:数据库主键对应java实体类的属性名。

  • 方式二 使用 selectKey 标签
<insert id="insertUser" parameterType="com.lwg.domain.User"  >
    <selectKey keyProperty="userId" keyColumn="user_id" resultType="int" order="AFTER">
        select last_insert_id()
    </selectKey>
    insert into user(user_name,password,nature) values (#{userName},#{password},#{nature})
</insert>

keyProperty:表示主键在Java实体类中对应的属性

keyColumn:表示主键在数据库的列名

resultType:返回类型,也就是keyProperty对应的类型

select last_insert_id() :查询最近一次插入的主键

注意:

如果id的值是字符串类型的uuid时,数据库就不能够主键自动生成,那么可以考虑使用方式二来实现

<insert id="insertUser2" parameterType="com.xyr.domain.User2">
    <selectKey resultType="java.lang.String" keyColumn="id" keyProperty="id" order="BEFORE"> 
        select uuid();
    </selectKey>
    insert into t_user(id, name, password, age) values(#{id}, #{name}, # {password}, #{age})
</insert>
删除操作

通过id删除用户

UserMapper接口代码

public int deleteByUserId(Integer userId);

UserMapper.xml代码

<delete id="deleteByUserId" parameterType="int" >
    delete from USER where user_id=#{userId}
</delete>

测试类

@Test
public void deleteByUserId(){
    userMapper.deleteByUserId(6);
}
更新操作

通过id更新用户

UserMapper接口代码

public int updateByUserId(User user);

UserMapper.xml代码

<update id="updateByUserId" parameterType="com.lwg.domain.User">
    update USER set user_name=#{userName},password=#{password} , nature=#{nature}
    where user_id=#{userId}
</update>

测试类代码

@Test
public void updateById(){
    User user=new User();
    user.setUserId(4);
    user.setUserName("无语");
    user.setPassword("jgjg");
    user.setNature("1");
    userMapper.updateByUserId(user);
}
查询操作

通过id查询单个用户

UserMapper接口代码

public User selectUserById(Integer userId);

UserMapper.xml代码

<select id="selectUserById" parameterType="int" resultMap="userMap">
    select * from USER where user_id=#{userId}
</select>

测试类代码

@Test
public void selectById(){
    User user = userMapper.selectUserById(1);
    System.out.println("user = " + user);
}

**注意:**查询操作属于DQL语句,不需要提交。

查询所有用户记录

UserMapper接口代码

public List<User> selectAll();

UserMapper.xml代码

<select id="selectAll" resultMap="userMap">
    select * from USER
</select>

测试类代码

@Test
public void selectAll(){
    List<User> users = userMapper.selectAll();
    System.out.println("users = " + users);
}
手动映射

当数据库里的字段名和Java实体类中的属性名不一致时,就不能自动映射,要手动映射。

手动映射UserMapper.xml文件代码

<resultMap id="userMap" type="com.lwg.domain.User">
    <id column="user_id" property="userId"></id>
    <result column="user_name" property="userName"/>
</resultMap>

resultMap:手动映射标签,里面的id属性唯一标识,type属性表示映射的java实体类

id标签表示该列是主键,column属性表示数据库列名,property属性名表示java实体类的属性名

result标签表示该列是普通字段

查询单个用户,使用手动映射

UserMapper接口代码

public User slecteltById(Integer id);

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.lwg.dao.UserMapper">
    <resultMap id="userMap" type="com.lwg.domain.User">
        <id column="user_id" property="userId"></id>
        <result column="user_name" property="userName"/>
    </resultMap>
     <select id="selectUserById" parameterType="int" resultMap="userMap">
        select * from USER where user_id=#{userId}
    </select>
    </mapper>

注意:

​ 使用了手动映射返回值的属性使用resultMap,而不是resultType;resultType和resultMap不能同时存在。

MyBatis核心配置

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

xk4pzn.png

注意:

​ 在书写配置文件时,每个配置的顺序必须遵循上面结构的顺序,比如properties标签要在settings标签上面。

properties属性配置

properties 用来配置mybatis主配置文件的全局属性, 可以用在当前文件的任意位置。

配置方式有两种

  • 方式一 通过标签内部配置
<configuration>
	<properties>
   	 <property name="mysql.driver" value="com.mysql.cj.jdbc.Driver"/>
   	 <property name="mysql.url" value="jdbc:mysql://localhost:3306/mybatis_db? useUnicode=true&amp;characterEncoding=utf8"/>
    	<property name="mysql.username" value="root"/>
    	<property name="mysql.password" value="20010107wdsr"/>
	</properties>
</configuration>
  • 方式二 通过属性文件进行外部配置
<properties resource="db.properties" />

db.properties配置文件代码:

mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis_db?useUnicode=true&characterEncoding=utf8
mysql.username=root
mysql.password=20010107wdsr

引用properties 定义的属性值格式:${属性名称}

<dataSource type="POOLED">
    <property name="driver" value="${mysql.driver}"/>
    <property name="url" value="${mysql.url}"/>
    <property name="username" value="${mysql.username}"/>
    <property name="password" value="${mysql.password}"/>
</dataSource>
settings设置配置

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

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true | falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true | falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true | falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION | STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true | falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true | falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB | JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true | falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置

完整的settings示例:

<settings>
    <!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 -->
  <setting name="logImpl" value="LOG4J"/>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
    <!-- 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。-->
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
    <!-- 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名aColumn。-->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
typeAliases别名配置

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

<typeAliases>
    <typeAlias type="com.lwg.domain.User" alias="User" />
    <typeAlias type="com.lwg.domain.User2" alias="User2" />
</typeAliases>

type属性表示缩写代表的类型

alias属性表示缩写,在其他映射里面可直接用这个名字代替

同时也可以配置整个包来设置当前包所有的类的别名,配置了包后,会自动将包中所有的类的别名设置为类名首字母小写。

<typeHandlers>
    <package name="com.lwg.domain"/>
</typeHandlers>

MyBatis内置了一些别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
typeHandlers类型处理器

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERICBYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERICSMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERICINTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERICBIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERICFLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERICDECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHARLONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE

可以重写类型处理器或创建自己的类型处理器来处理一些不支持的或非标准的类型。做法是:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler , 并且可以(可选地)将它映射到一个 JDBC 类型。

案例

1、数据存储时,自动将字符串集合转换成字符串(用逗号作为分隔符)。

2、数据读取时,将查询的出来的字符串自动映射到集合里。

实现:

新User类一个属性hobbies,并且同步在数据库user表新增hobbies字段。

User类代码

package com.lwg.domain;

import java.util.List;

public class User {
    private int userId;
    private String userName;
    private String password;
    private String nature;
    private List<String> hobbies;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getNature() {
        return nature;
    }

    public void setNature(String nature) {
        this.nature = nature;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", nature='" + nature + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

新增一个类作为类型处理器,该类实现TypeHandler接口,并重写它的setParameter、getResult方法。

package com.lwg.typehandler;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @author lwg
 * @create 2022/9/23 19:32
 * 类型转换器,List集合转换成VARCHAR类型
 */

@MappedJdbcTypes(JdbcType.VARCHAR)  //设置数据库对应的类型
@MappedTypes(List.class)  //设置在JAVA类中对应的类型
public class List2VarcharHandler implements TypeHandler<List<String>> {
    //设置参数,将集合的数据拼接成字符串,分隔符用逗号,然后将这个字符串保存在hobbies属性里
    @Override
    public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        StringBuilder stringBuilder = new StringBuilder();
        for (String s : parameter) {
            stringBuilder.append(s);
            stringBuilder.append(",");
        }
        stringBuilder.delete(stringBuilder.lastIndexOf(","),stringBuilder.length());
        System.out.println("stringBuilder = " + stringBuilder);
        ps.setString(i,stringBuilder.toString());
    }
//getResult将结果集处理成字符串集合
//通过列名赋值
    @Override
    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
        String string = rs.getString(columnName);
        if(Objects.nonNull(string)){
            String[] strings = string.split(",");
            List<String> list = Arrays.asList(strings);
            return list;
        }
        return null;
    }
//通过索引赋值
    @Override
    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
        String string = rs.getString(columnIndex);
        if (Objects.nonNull(string)) {
            String[] split = string.split(",");
            List<String> list = Arrays.asList(split);
            return list;
        }
        return null;
    }

    @Override
    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String string = cs.getString(columnIndex);
        if (Objects.nonNull(string)) {
            String[] split = string.split(",");
            List<String> list = Arrays.asList(split);
            return list;
        }
        return null;
    }
}

编写UserMapper接口

public int insertUserH(User user);

编写UserMapper.xml

<insert id="insertUserH" parameterType="User">
    insert into USER(user_name,password,nature,hobbies) values(#{userName},#{password},#{nature},#{hobbies,typeHandler=com.lwg.typehandler.List2VarcharHandler})
</insert>

typeHandler属性表示设置类型处理器

编写测试类

@Test
public void insertHandler(){
    User user=new User();
    user.setUserName("一环");
    user.setPassword("1234");
    user.setNature("1");
    List<String> hobbis=new ArrayList<>();
    hobbis.add("游泳");
    hobbis.add("打球");
    hobbis.add("骑车");
    user.setHobbies(hobbis);
    userMapper.insertUserH(user);
}

添加、修改可以将字符串集合转换成字符串,但查询时依旧无法完成自动映射,还需要在UserMapper.xml里设置resultMap。

<resultMap id="userMap" type="User">
    <id column="user_id" property="userId"></id>
    <result column="user_name" property="userName"/>
    <result column="hobbies" property="hobbies" typeHandler="com.lwg.typehandler.List2VarcharHandler" />
</resultMap>

当然也可以直接在全局配置(mybatis-config.xml)里设置

<typeHandlers>
    <typeHandler handler="com.lwg.typehandler.List2VarcharHandler" />
</typeHandlers>

当自定义类型处理器多个时,将所有类型处理器处在的包作为配置,这样包下的的类型处理器都配置了

<typeHandlers>
    <package name="com.lwg.typehandler"/>
</typeHandlers>
environments 环境配置

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

**注意:**尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

如果要连接两个数据库,就要有两个 SqlSessionFactory 实例,每个实例对应一个数据库,多个就对应多个。

指定创建哪种环境的SqlSessionFactory 实例,只要将它作为可选参数传给SqlSessionFactoryBuilder 即可。

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果不写环境配置,会加载默认环境

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

environments 元素定义了如何配置环境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>
  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。
transactionManager 事务管理器

MyBatis定义了两种事务管理器 type=“[JDBC|MANAGED]”

JDBC :这个配置直接使用JDBC的提交和回滚

MANAGED:这个配置不设置事务的提交和回滚,而是交给容器管理事务的整个周期。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>		
dataSource 数据源

用来配置JDBC连接对象,有三种内建的数据源类型: type=“[UNPOOLED|POOLED|JNDI]”

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

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

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

databaseIdProvider数据库厂商标识

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />

databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为
DatabaseMetaData#getDatabaseProductName() 返回的字符串。 由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

在提供了属性别名时,databaseIdProvider 的 DB_VENDOR 实现会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 “null”。 在这个例子中,如果 getDatabaseProductName() 返回“Oracle (DataDirect)”,databaseId 将被设置为“oracle”。你可以通过实现接口org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
    // 空实现
  }
  String getDatabaseId(DataSource dataSource) throws SQLException;
}
mappers映射器配置

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

xml配置:

<mappers>
  <mapper resource="com/xyr/dao/AuthorMapper.xml"/>
  <mapper resource="com/xyr/dao/BlogMapper.xml"/>
  <mapper resource="com/xyr/dao/PostMapper.xml"/>
</mappers>

当然也可以设置扫描包下所有,配置包下的所有为映射配置文件

<mappers>
   <package name="com.lwg.dao"/>
</mappers>

注解配置

<mappers> 
    <mapper class="com.xyr.dao.UserMapper"/> 
    <mapper class="com.xyr.dao.OrderMapper"/>
    <mapper class="com.xyr.dao.EmployeeMapper"/>
</mappers>

MyBatis基于注解配置实现CRUD

添加操作

添加用户

UserMapper接口:

@Insert("INSERT INTO USER(user_name,PASSWORD,nature) VALUES(#{userName},#{password},#{nature})")
public int insertUserJ(User user);
删除操作

删除用户

UserMapper接口:

@Delete("DELETE FROM USER WHERE user_id=#{userId}")
public int deleteByUserIdJ(Integer userId);
更新操作

更新用户

UserMapper接口

@Update("UPDATE USER SET user_name=#{userName},password=#{password},nature=#{nature} where user_id=#{userId}")
public int upDateJ(User user);
查询操作

根据id查询用户

@Select("select * from USER where user_id=#{userId}")
public User selectUserByUserIdJ(Integer userId);
手动映射
@Results(id = "userMap",value = {
        @Result(id = true,column = "user_id",property = "user_name"),
        @Result(column = "user_name",property = "userName")
})
  @Select("select * from USER where user_id=#{userId}")
    @ResultMap("userMap")
    public User selectUserByUserIdJ(Integer userId);

MyBatis多参数开发

MyBatis默认不支持多参数开发,默认情况下将多参数打包在一个Map里面,然后多参数默认的参数名称是arg0、arg1、arg…,或者 param1、param2、param…

多参数开发解决:

方式一

参数名称使用arg0、arg1、arg…

UserMapper接口

@Select("select * from USER where user_id=#{arg0} and user_name=#{arg1}")
@ResultMap("userMap")
public User selectUserByIdName(Integer userId,String userName);
方式二

参数名称使用 param1、param2、param…

UserMapper接口

@Select("select * from USER where user_id=#{param1} and user_name=#{param2}")
@ResultMap("userMap")
public User selectUserByIdName(Integer userId,String userName);
方式三

使用@Param注解给参数起名称,访问参数使用起的这个名称。

UserMapper接口

@Select("select * from USER where user_id=#{userId} and user_name=#{userName}")
@ResultMap("userMap")
public User selectUserByIdName(@Param("userId") Integer userId,@Param("userName") String userName);
方式四

参数打包成对象

UserMapper接口

@Select("select * from USER where user_id=#{user.userId} and user_name=#{user.userName}")
   @ResultMap("userMap")
   public User selectUserByIdName(@Param("user") User user);

测试代码

@Test
public void selectByidName(){
    User user1=new User();
    user1.setUserId(4);
    user1.setUserName("无语");
    User user = userMapper.selectUserByIdName(user1);
    System.out.println(user);
}
方式五

参数打包成Map中,通过键取值

UserMapper接口

@Select("select * from USER where user_id=#{userId} and user_name=#{userName}")
@ResultMap("userMap")
public User selectUserByIdName(Map<String,Object> user);

测试类

@Test
public void selectByidName(){
    Map<String,Object> map=new HashMap<>();
    map.put("userId",4);
    map.put("userName","无语");
    User user = userMapper.selectUserByIdName(map);
    System.out.println(user);
}

MyBatis参数传递规律:

  • 一个参数时,如果是基本类型,直接获取。如果是对象或Map,使用对象属性名或键取值,如果是数组直接使用array来获取,如果是list直接使用list或者collection获取,如果是set集合,只有使用collection获取,也可以用注解@param来定义名称。
  • 多个参数在未使用@param定义名称时,默认是获取的键是arg0, arg1, param1, param2,可以使用定义名称,使用定义的名称取值。

$和#的区别

#{id} 表示告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码... 
String sql = "SELECT * FROM USER WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1,id);

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接拼接字符串。 比如 ORDER BY 子句,这时候你就必须要使用到 ${} 的语法方式。

$是拼接字符串,#是设置占位字符

总结:

两者的核心区别其实就是 StatementPreparedStatement 之间的区别,一个设置占位符,一个字符串拼接。设置占位符的方式性能高,安全性高,防止SQL注入,而拼接字符串的方式在某些场合也能用得上。

Mapper.xml一些重要属性

parameterType

表示指明传入的参数类型。这里可以是基本类型(可以省略),字符串,集合或对象。

resultType

表示指明返回值类型,这里可以基本类型,也可以是集合,对象类型。

:当返回值的类型是List<>或User[]类型时,要写的是List里的元素类型或数组的类型。比如List要写User。

resultMap

指明手动映射,对结果集的自动映射。

:resultType和resultMap只能同时使用一个。

基于XML配置实现动态SQL

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

动态SQL元素是借助了OGNL的表达式

扩展:

OGNL基本表达式:

获取对象: u s e r 、 {user} 、 user{user.name} 、${user.dog.age}

获取map: ${key} 、 ${key.name}

获取集合或数组:${arry[1]}

if

判断条件是否成立,成立则拼接对应的sql语句。

<if test="条件">
sql语句
</if>
where
  • 会自动拼接where,会根据里面的条件是否满足进行拼接where
  • 如果条件前面有and/or会自动删除,保证语句的正确
  • 会自动删除多余的空格、逗号,也会自动补上缺的逗号、空格
  • 如果没有一个条件不会拼where
<where>
sql语句
</where>

多条件查询

多条件查询用户记录

UserMapper接口

public List<User> selectMultiple(Map<String,Object> condition);

UserMapper.xml

<select id="selectMultiple" resultMap="userMap" parameterType="map">
    select * from USER
    <where>
        <if test="userName !=null and userName != ''">
            and user_name =#{userName}
        </if>
        <if test="password !=null and password != ''">
            and password=#{password}
        </if>
    </where>
</select>

测试类

@Test
public void selectCondition(){
    Map<String,Object> conditionMap=new HashMap<>();
    conditionMap.put("userName","令狐冲");
    conditionMap.put("password","123456");
    List<User> users = userMapper.selectMultiple(conditionMap);
    System.out.println(users);
}
set
  • 会自动拼接set,根据是否满足条件进行拼接set
  • 会自动删除多余的空号和逗号
  • 会自动补充缺的空号和逗号
<set>
sql语句
</set>

更新用户记录

根据用户传入的信息进行更新,用户传入的信息为空就不更新那个属性,不为空就更新这个属性。

UserMapper接口

public int updateUserMultiple(@Param("user") User user);

UserMapper.xml

<update id="updateUserMultiple" parameterType="User">
    update USER
    <set>
        <if test="user.userName !=null and user.userName !=''">
            user_name=#{user.userName},
        </if>
        <if test="user.password !=null and user.password !=''">
            password=#{user.password},
        </if>
        <if test="user.nature !=null and user.nature !=''">
            nature=#{user.nature},
        </if>
    </set>
    where user_id=#{user.userId}
</update>

测试类

 @Test
    public void updateUserMultipleTest(){
        User user=new User();
        user.setUserId(7);
        user.setUserName("d鸡肉");
//        user.setPassword("123456");
//        user.setNature("0");
        userMapper.updateUserMultiple(user);

    }
trim
  1. 删除sql前面指定的prefixOverrides字符串并且在最前面添加指定的prefix字符串。
  2. 删除sql后面指定的suffixOverrides字符串并且在最后面添加指定的suffix字符串。
  3. prefixOverrides或者suffixOverrides如果需要处理多个字符使用 | 隔开。
  4. trim标签只能够处理前缀和后缀,是不能够处理中间的SQL,中间SQL自行处理
<trim 属性=属性值>
sql语句
</trim>

trim属性:

prefix:配置在sql语句前面加的前缀

prefixOverrides:配置删除sql前缀多余的字符串

suffix:配置在sql语句后面加的后缀

suffixOverrides:配置删除sql后缀多余的字符串

将上面多条件查询用户记录使用trim标签替换where标签编写动态SQL

UserMapper.xml

<select id="selectMultiple" resultMap="userMap" parameterType="map">
     select * from USER
     <trim prefix="where" prefixOverrides="and|or">
         <if test="userName !=null and userName != ''">
             and user_name =#{userName}
         </if>
         <if test="password !=null and password != ''">
             and password=#{password}
         </if>
     </trim>
</select>

上面更新用户操作使用trim标签替换set标签编写动态SQL

UserMapper.xml

<update id="updateUserMultiple" parameterType="User">
     update USER
     <trim prefix="set" suffixOverrides=",">
         <if test="user.userName !=null and user.userName !=''">
             user_name=#{user.userName},
         </if>
         <if test="user.password !=null and user.password !=''">
             password=#{user.password},
         </if>
         <if test="user.nature !=null and user.nature !=''">
             nature=#{user.nature},
         </if>
     </trim>
    where user_id=#{user.userId}
</update>
foreach

对集合进行遍历,在构建In条件语句的时候。

<foreach 属性=属性值>
sql语句
</foreach>

foreach属性:

item:表示集合每一个元素迭代的别名,在使用引用到每一个元素就用这个别名。

index:索引的别名,在使用索引时就使用这个别名。

open:表示拼接的SQL语句以什么字符开头。

separator:表示每次迭代完以什么字符分隔。

close:表示语句以什么字符结尾。

collection:该参数是必须要有的,表示参数类型。

批量删除用户记录

UserMapper接口

public int deleteByUserIdMultiple(List<Integer> userIds);

UserMapper.xml

<delete id="deleteByUserIdMultiple" parameterType="list">
    delete from USER where user_id in
    <foreach collection="list" item="userId" open="(" close=")" separator="," >
        #{userId}
    </foreach>
</delete>

测试类

@Test
public void deleteUserMultipleTest(){
    List<Integer> userIds=new ArrayList<>();
    userIds.add(4);
    userIds.add(5);
    userIds.add(7);
    userMapper.deleteByUserIdMultiple(userIds);
}

批量插入用户记录

UserMapper接口

public int insertUserMultiple( List<User> users);

UserMapper.xml

<insert id="insertUserMultiple" parameterType="list">
    insert into USER(user_name,password,nature) values
    <foreach collection="list" item="user" separator=",">
        (#{user.userName},#{user.password},#{user.nature})
    </foreach>
</insert>

测试类

@Test
public void insertUserMultipleTest(){
    List<User> users=new ArrayList<>();
    User user1=new User();
    user1.setUserName("王华bb");
    user1.setPassword("23455");
    user1.setNature("4");
    User user2=new User();
    user2.setUserName("又s又");
    user2.setPassword("235322455");
    user2.setNature("1");
    User user3=new User();
    user3.setUserName("sft华子");
    user3.setPassword("0976");
    user3.setNature("1");
    users.add(user1);
    users.add(user2);
    users.add(user3);
    userMapper.insertUserMultiple(users);

}

基于注解配置实现动态SQL

查询使用注解@SelectProvider(type=xxx.class,method=“…”)

修改使用注解@UpdateProvider(type = xxx.class,method = “…”)

删除使用注解@DeleteProvider(type = xxx.class,method = “…”)

插入使用注解@InsertProvider(type = xxx.class,method = “…”)

使用@SelectProvider需要自己手写类方法实现sql语句的拼接,可以实现通过MyBatis提供的SQL API来帮助我们更好地生成动态SQL。

属性介绍:

type:实现动态拼接SQL的类

method:实现的方法名

多条件查询用户记录

UserMapper接口

@ResultMap("userMap")
@SelectProvider(type = UserProvider.class,method ="selectUserByCondition" )
public List<User> selectUserByCondition(User condition);

注:

接口参数要和method属性里的方法的参数类型形参名一致。

UserProvider类

package com.lwg.provider;

import com.lwg.domain.User;
import org.apache.ibatis.jdbc.SQL;

import java.util.Objects;

/**
 * @author lwg
 * @create 2022/9/30 10:44
 */
public class UserProvider {
    public String selectUserByCondition(User condition){
        SQL sql=new SQL();
        sql.SELECT("*");
        sql.FROM("USER");
        if (condition.getUserName()!=null && condition.getUserName().length()>0) {
            sql.AND().WHERE("user_name like  concat('%',#{userName},'%')");
        }
        if (condition.getPassword()!=null && condition.getPassword().length()>0) {
            sql.AND().WHERE("password=#{password}");
        }
        if (condition.getNature()!=null&& condition.getNature().length()>0) {
            sql.AND().WHERE("nature=#{nature}");
        }
        System.out.println("sql = " + sql.toString());
        return sql.toString();
    }
}

测试类

 @Test
    public void selectUserByConditionTest(){
        User user=new User();

        user.setUserName("华");
//        user.setPassword("13456");
        user.setNature("1");
        List<User> users = userMapper.selectUserByCondition(user);
        System.out.println("users = " + users);

    }

多条件查询用户记录条数

UserMapper接口

@SelectProvider(type = UserProvider.class,method ="selectUserByConditionNumber" )
public int selectUserByConditionNumber(User condition);

UserProvider类的selectUserByConditionNumber方法

public String selectUserByConditionNumber(User condition){
    SQL sql=new SQL();
    sql.SELECT("count(*)");
    sql.FROM("USER");
    if (condition.getUserName()!=null && condition.getUserName().length()>0) {
        sql.AND().WHERE("user_name like  concat('%',#{userName},'%')");
    }
    if (condition.getPassword()!=null && condition.getPassword().length()>0) {
        sql.AND().WHERE("password=#{password}");
    }
    if (condition.getNature()!=null&& condition.getNature().length()>0) {
        sql.AND().WHERE("nature=#{nature}");
    }
    System.out.println("sql = " + sql.toString());
    return sql.toString();
}

测试类

    @Test
    public void selectUserByConditionNumberTest(){
        User user=new User();

        user.setUserName("华");
//        user.setPassword("13456");
//        user.setNature("1");
        int i = userMapper.selectUserByConditionNumber(user);
        System.out.println("i = " + i);


    }

更新用户

根据传入的值做更新操作,如果该值为Null就不更新该属性。

UserMapper接口

@UpdateProvider(type = UserProvider.class,method = "updateUserCondition")
public int updateUserCondition(User user);

UserProvider的updateUserCondition方法

public String updateUserCondition(User user){
    if (!Objects.nonNull(user)) {
        System.out.println("参数不能为空");
    }
    SQL sql=new SQL();
    sql.UPDATE("USER");
    if (user.getUserName()!=null && user.getUserName()!="") {
        sql.SET("user_name=#{userName}");
    }
    if (user.getPassword()!=null && user.getPassword()!="") {
        sql.SET("password=#{password}");
    }
    if (user.getNature()!=null && user.getNature()!="") {
        sql.SET("nature=#{nature}");
    }
    sql.WHERE("user_id=#{userId}");
    return sql.toString();
}

测试类

 @Test
    public void updateUserConditionTest(){
        User user=new User();
        user.setUserId(16);
        user.setUserName("gg宁宁");
//        user.setPassword("46778");
        user.setNature("9");
        userMapper.updateUserCondition(user);

    }

批量删除用户记录

UserMapper接口

@DeleteProvider(type = UserProvider.class,method = "deleteUsersCondition")
public int deleteUsersCondition(@Param("ids")List<Integer> ids);

UserProvider类的deleteUsersCondition方法

public String deleteUsersCondition(@Param("ids")List<Integer> ids){
    SQL sql=new SQL();
    sql.DELETE_FROM("USER");
    StringBuilder sb=new StringBuilder();
    sb.append("(");
    for (int i=0;i<ids.size();i++){
        sb.append("#{ids["+i+"]},");
    }
    sb.deleteCharAt(sb.lastIndexOf(","));
    sb.append(")");
    sql.WHERE("user_id in".concat(sb.toString()));
    return sql.toString();
}

注:

当接口方法使用了@Param为参数起别名,Provider中的方法参数也要@Param(“别名”)。

测试类

@Test
public void deleteUsersConditionTest(){
    List<Integer> ids=new ArrayList<>();
    ids.add(14);
    ids.add(15);
    ids.add(16);
    userMapper.deleteUsersCondition(ids);
}

批量插入用户记录

UserMapper接口

@InsertProvider(type = UserProvider.class,method = "insertUserBatch")
public int insertUserBatch(@Param("users") List<User> userList);

UserProvider类中的insertUserBatch方法

public String insertUserBatch(@Param("users") List<User> userList){
    SQL sql=new SQL();
    sql.INSERT_INTO("USER(user_name,password,nature)");
    for (int i=0;i<userList.size();i++){
        sql.INTO_VALUES("#{users["+i+"].userName}");
        sql.INTO_VALUES("#{users["+i+"].password}");
        sql.INTO_VALUES("#{users["+i+"].nature}");
        sql.ADD_ROW();//添加新的一行数据,以便执行批量插入
    }
    return sql.toString();
}

测试类

@Test
public void insertUserBatchTest(){
    List<User> users=new ArrayList<>();
    User user1=new User();
    user1.setUserName("欢迎");
    user1.setPassword("jfjfjfj99");
    user1.setNature("0");
    User user2=new User();
    user2.setUserName("夏明翰");
    user2.setPassword("j99");
    user2.setNature("1");
    User user3=new User();
    user3.setUserName("却时");
    user3.setPassword("0987");
    user3.setNature("2");
    users.add(user1);
    users.add(user2);
    users.add(user3);
    userMapper.insertUserBatch(users);
}

MyBatis基于XML实现多表查询

关系模型和对象模型的建立

多表查询分为四种情况:

  • 一对一(公民=身份证号码)
  • 一对多(班级=学生,部门=员工)
  • 多对一(学生=班级,员工=部门)
  • 多对多(老师=学生,订单=商品)

这种关系为概念模型,概念模型在数据库里称为关系模型,在Java中称为对象模型。

一对一模型建立

一对一关系模型建立(丈夫=妻子)

CREATE TABLE husband(
h_id INT PRIMARY KEY,
h_name VARCHAR(20)
);
CREATE TABLE wife(
w_id INT PRIMARY KEY,
w_name VARCHAR(20),
FOREIGN KEY(w_id) REFERENCES husband(h_id)
)
INSERT INTO  husband(h_id,h_name) VALUES
(1,'杨过'),
(2,'郭靖'),
(3,'进行')

INSERT INTO  wife(w_id,w_name) VALUES
(1,'小龙女'),
(2,'黄容'),
(3,'停止')

总结:在一张表设置既是主键也是外键。

一对一对象模型建立

Husband实体类

public class Husband {
    private Integer hId;
    private String hName;
    private Wife wife;
}

Wife实体类

public class Wife {
    private Integer wId;
    private String wName;
    private Husband husband;
}
一对多模型建立

一对多关系模型建立(部门=员工)

CREATE TABLE department(
d_id INT PRIMARY KEY,
d_name VARCHAR(20)
);
CREATE TABLE employee(
e_id INT PRIMARY KEY,
e_name VARCHAR(20),
deptno INT,
FOREIGN KEY(deptno) REFERENCES department(d_id)
);

总结:

多方设置外键,引用一方主键。

一对多对象模型建立

Department实体类

public class Department {
    private Integer dId;
    private String dName;
    private List<Employee> employees;
}

Employee实体类

public class Employee {
    private Integer eId;
    private String eName;
    private Department department;
}
多对多模型建立

多对多关系模型建立(老师=学生)

CREATE TABLE teacher(
t_id INT PRIMARY KEY,
t_name VARCHAR(20)
);
CREATE TABLE student(
s_id INT PRIMARY KEY,
s_name VARCHAR(20)
);
CREATE TABLE stu_tea(
t_id INT,
s_id INT,
FOREIGN KEY(t_id) REFERENCES teacher(t_id),
FOREIGN KEY(s_id) REFERENCES student(s_id)
)

总结:建立第三张表作为第三方表,通过该表的字段作为外键关联另外两张表的主键来建立多对多关系。

多对多对象模型建立

Student实体类

public class Student {
    private Integer sId;
    private String sName;
    private List<Teacher> teachers;
}

Teacher实体类

public class Teacher {
    private Integer tId;
    private String tName;
    private List<Student> students;
}
MyBatis映射关系

MyBatis将数据模型转换为对象模型的过程称为映射关系,有两种映射关系:

  • 一对一(one)
  • 一对多(many)
MyBatis实现一对一映射

案例

通过id查询单个员工,以及员工及在部门的的信息。

创建数据库表对应的实体类

Department实体类

public class Department {
    private Integer dId;
    private String dName;
    private List<Employee> employees;
}

注:对象模型不依赖外键,只依赖引用类型。

Employee实体类

public class Employee {
    private Integer eId;
    private String eName;
    private Department department;
}

创建EmployeeMapper接口

public interface EmployeeMapper {
    public Employee selectEmployeeById(Integer eId);
}

创建EmployeeMapper.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.lwg.dao.EmployeeMapper">

    <resultMap id="employeeMap" type="Employee">
        <id column="e_id" property="eId" />
        <result column="e_name" property="eName" />
    </resultMap>

    <select id="selectEmployeeById" parameterType="int" resultMap="employeeMap">
      
    </select>

</mapper>

直接写普通的sql查询语句是无法查询出员工的部门信息,要使用连接,因为要保证员工的完整性,使用左外连接。

查询出员工的部门信息还要将部门信息实现映射,将查询到的部门信息映射到Employee实体类的department属性里。

方式一 :别名自动映射

在书写查询sql语句是起别名,别名与department类的实体类的属性名对应。

<select id="selectEmployeeById" parameterType="int" resultMap="employeeMap">
        SELECT e.*,d.d_id 'department.dId',d.d_name 'department.dName' FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
       WHERE e.e_id=#{eId}
    </select>

方式二:定义resultMap,别名手动映射

<resultMap id="employeeMapBase" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
</resultMap>
<resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
    <result column="d_id" property="department.dId"/>
    <result column="d_name" property="department.dName" />
</resultMap>
 <select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
         SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
         where e.e_id=#{eId}
    </select>

方式三:使用association标签处理一对一映射关系

<resultMap id="employeeMap" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
    <association property="department" javaType="Department" columnPrefix="d_" >
        <id column="id" property="dId" />
        <result column="name" property="dName" />
    </association>
</resultMap>

<select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
     SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
     where e.e_id=#{eId}
</select>

association标签属性:

columnPrefix:表示数据库字段前缀,可有可无。

上面的配置还可以写成

<resultMap id="employeeMap" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
    <association property="department" javaType="Department"  >
        <id column="d_id" property="dId" />
        <result column="d_name" property="dName" />
    </association>
</resultMap>

<select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
     SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
     where e.e_id=#{eId}
</select>

也可以优化成这样

<resultMap id="employeeMapBase" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
</resultMap>
 <resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
        <association property="department" javaType="Department"  >
            <id column="d_id" property="dId" />
            <result column="d_name" property="dName" />
        </association>
    </resultMap>
     <select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
         SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
         where e.e_id=#{eId}
    </select>

优化之后代码复用性更强。

extends:表示继承这个Map。

还可以优化成:

创建DepartmentMapper.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.lwg.dao.DepartmentMapper">
    <resultMap id="departmentMap" type="Department">
        <id column="d_id" property="dId" />
        <result column="d_name" property="dName" />
    </resultMap>
</mapper>

修改EmployeeMapper.xml

<resultMap id="employeeMapBase" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
</resultMap>
<resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
        <association property="department" javaType="Department" resultMap="com.lwg.dao.DepartmentMapper.departmentMap" >
        </association>
    </resultMap>
 <select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
         SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
         where e.e_id=#{eId}
    </select>

方式四:使用两次查询

<resultMap id="employeeMapBase" type="Employee">
    <id column="e_id" property="eId" />
    <result column="e_name" property="eName" />
</resultMap>
 <resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
        <association property="department" javaType="Department" select="com.lwg.dao.DepartmentMapper.selectDepartmentById" column="deptno" >
        </association>
    </resultMap>
<select id="selectEmployeeById" resultMap="employeeMap"  parameterType="int">
         SELECT * FROM employee WHERE e_id=#{eId}
    </select>

注:

column:表示selectDepartmentById方法参数的由现在这个查询的哪个字段提供,如果有多个参数,中间用逗号分隔开。

DepartmentMapper.xml代码

<resultMap id="departmentMap" type="Department">
    <id column="d_id" property="dId" />
    <result column="d_name" property="dName" />
</resultMap>
<select id="selectDepartmentById" parameterType="int" resultMap="departmentMap">
    select * from department where d_id=#{dId}
</select>

这种方式本质是执行了两次SQL语句,这种方式我们可以设置懒加载,也就是对象被访问了才执行对应的SQL,通过 fetchType 属性设置为 lazy 来实现这种机制。代码如下:

<resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
    <association property="department" javaType="Department" select="com.lwg.dao.DepartmentMapper.selectDepartmentById" column="deptno" fetchType="lazy">
    </association>
</resultMap>

当查询对象没有访问时,只查询第一次

测试类代码:

    @Test
    public void selectEmployeeByIdTest(){
        Employee employee = employeeMapper.selectEmployeeById(1);
//        System.out.println("employee = " + employee);
    }

执行结果:

xQw4KI.png

当查询对象被访问时,查询两次

测试类代码

@Test
public void selectEmployeeByIdTest(){
    Employee employee = employeeMapper.selectEmployeeById(1);
    System.out.println("employee = " + employee);
}

执行结果:

xQwIqP.png

MyBatis实现一对多映射

通过部门id查询某个部门的信息及部门下的所有员工的信息

Department实体类

public class Department {
    private Integer dId;
    private String dName;
    private List<Employee> employees;
}

Employee实体类

public class Employee {
    private Integer eId;
    private String eName;
    private Department department;
}

创建DepartmentMapper接口

package com.lwg.dao;

import com.lwg.domain.Department;

/**
 * @author lwg
 * @create 2022/10/3 19:52
 */
public interface DepartmentMapper {
    
}

方式一:使用collection标签

和association标签一样,association标签用于对象,collection标签用于集合。

DepartmentMapper.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.lwg.dao.DepartmentMapper">
     <resultMap id="departmentBase" type="Department">
        <id column="d_id" property="dId" />
        <result column="d_name" property="dName" />
    </resultMap>
    <resultMap id="departmentMap" type="Department" extends="departmentBase">
        <collection property="employees" resultMap="com.lwg.dao.EmployeeMapper.employeeMapBase"   ofType="Employee" />
    </resultMap>
    <select id="selectDepartmentById" parameterType="int" resultMap="departmentMap">
        select d.*,e.* from department d left join employee e on d.d_id=e.deptno where d.d_id=#{dId}
    </select>
    
</mapper>

EmployeeMapper.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.lwg.dao.EmployeeMapper">

    <resultMap id="employeeMapBase" type="Employee">
        <id column="e_id" property="eId" />
        <result column="e_name" property="eName" />
    </resultMap>
  <!--  <resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
        <result column="d_id" property="department.dId"/>
        <result column="d_name" property="department.dName" />
    </resultMap>-->

    <resultMap id="employeeMap" type="Employee" extends="employeeMapBase">
        <association property="department"  javaType="Department" select="com.lwg.dao.DepartmentMapper.selectDepartmentById" column="deptno" fetchType="lazy">
        </association>
    </resultMap>

   <!-- <select id="selectEmployeeById" parameterType="int" resultMap="employeeMap">
        SELECT e.*,d.d_id 'department.dId',d.d_name 'department.dName' FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
       WHERE e.e_id=#{eId}
    </select>-->

    <!--<resultMap id="employeeMap" type="Employee">
        <id column="e_id" property="eId" />
        <result column="e_name" property="eName" />
        <association property="department" javaType="Department"  >
            <id column="d_id" property="dId" />
            <result column="d_name" property="dName" />
        </association>
    </resultMap>-->

   <!-- <select id="selectEmployeeById" resultMap="employeeMap" parameterType="int">
         SELECT e.*,d.d_id ,d.d_name  FROM employee e LEFT JOIN department d ON e.deptno=d.d_id
         where e.e_id=#{eId}
    </select>-->

    <select id="selectEmployeeById" resultMap="employeeMap"  parameterType="int">
         SELECT * FROM employee WHERE e_id=#{eId}
    </select>

    <select id="selectEmployeeAll" resultMap="employeeMap">
        select * from employee
    </select>

</mapper>

方式二:采用二次查询

DepartmentMapper.xml配置

<resultMap id="departmentBase" type="Department">
    <id column="d_id" property="dId" />
    <result column="d_name" property="dName" />
</resultMap>
 <resultMap id="departmentMap" type="Department" extends="departmentBase" >
        <collection property="employees" ofType="Employee" select="com.lwg.dao.EmployeeMapper.selectEmployeeByDeptId" column="d_id" />
    </resultMap>
     <select id="selectDepartmentById" parameterType="int" resultMap="departmentMap">
        select * from department where d_id=#{dId}
    </select>

这里也可以设置懒加载,设置 fetchType 属性为 lazy 来实现懒加载效果。

EmployeeMapper.xml代码如下:

 <resultMap id="employeeMapBase" type="Employee">
        <id column="e_id" property="eId" />
        <result column="e_name" property="eName" />
    </resultMap>
<select id="selectEmployeeByDeptId" parameterType="int" resultMap="employeeMapBase" >
    select * from employee where deptno=#{dId}
</select>

MyBatis基于注解配置实现多表查询

MyBatis注解开发多表查询只支持N+1(先查主表再查子表)次查询,不支持连接查询。

MyBatis注解实现一对一映射

通过id查询员工及员工所在的部门的信息

EmployeeMapper接口

public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE e_id=#{eId}")
    @Results({
            @Result(id = true,property = "eId",column = "e_id"),
            @Result(property = "eName",column = "e_name"),
            @Result(property = "department",column = "deptno",one = @One(select = "com.lwg.dao.DepartmentMapper.selectDepartmentByIdAnnotation"))
    })
    public Employee selectEmployeeByIdAnnotation(Integer eId);
}

​ 使用one属性使用@One注解

​ column属性表示传入参数是用列字段提供。

DepartmentMapper接口代码如下:

public interface DepartmentMapper { 
 @Select("SELECT * FROM department WHERE d_id=#{dId}")
    @Results(id = "departmentMap4",value = {
            @Result(id = true,column = "d_id",property = "dId"),
            @Result(column = "d_name",property = "dName")
    })
    public Department selectDepartmentByIdAnnotation(Integer dId);
}
MyBatis实现一对多映射

通过id查询部门信息以及部门员工信息。

DepartmentMapper接口

public interface DepartmentMapper {
  @Select("SELECT * FROM department WHERE d_id=#{dId}")
    @Results(id = "departmentMap4",value = {
            @Result(id = true,column = "d_id",property = "dId"),
            @Result(column = "d_name",property = "dName"),
            @Result(column = "d_id",property = "employees",many = @Many(select = "com.lwg.dao.EmployeeMapper.selectEmployeeByDidAnnotation"))
    })
    public Department selectDepartmentByIdAnnotation(Integer dId);


}

many使用@Many注解

EmployeeMapper接口的代码如下:

public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE deptno=#{dId}")
    @Results(id = "resAnnotation",value = {
            @Result(id = true,column = "e_id",property = "eId"),
            @Result(column = "e_name",property = "eName")
    })
    public List<Employee> selectEmployeeByDidAnnotation(Integer dId);
}

缓存

在MyBatis里,缓存就是将之前查过的记录放在内存的缓冲区或者文件上,如果再次查询,可以通过配置的策略,命中已经查询过的记录,从而提高查询记录。

MyBatis有两种缓存机制:

  • 一级缓存
  • 二级缓存
一级缓存

一级缓存就是会话(SqlSession)级别的缓存,就是同一个会话,如果已经查询过的记录数据就会保存一份在内存里,如果会话没有关闭,再次调用同样的方法查询,不会再去查询数据库,直接在缓存里取出之前查询的数据。

一级缓存原理

xliPWn.png

第一次访问数据库的执行查询时候,会直接执行SQL语句,返回的数据映射到Java对象中,并且保存到Sqlsession会话中。

当第二次再次调用相同的方法执行时,首先会去缓存中查找是否存在,如果存在直接在缓存中获取,如果不存在,再去请求数据库,并且在请求结束后,会把最新的数据缓存到Sqlsession会话中。

注:一级缓存默认是打开的,是无法关闭的。

一级缓存是关闭不了的,可以清除缓存一级缓存。

清除缓存

  1. 关闭sqlsession会话。
  2. 执行DML(增删改)操作,并且调用commit()方法提交。
  3. 手动调用用clearCache方法清除缓存。
@Test
public void selectDepartmentByIdAnnotationT(){
    Department department = departmentMapper.selectDepartmentByIdAnnotation(1);
    System.out.println("第一次" + department);
    department.setdId(2);
    sqlSession.clearCache(); //清除缓存
    System.out.println("第二次" + department);

}

运行结果:执行了两次sql语句,访问两次数据库。

xliH7F.png

如果不想每次查询都手动调用方法清除缓存,可设置配置将本地缓存作用域配置为STATEMENT,每次查询都会自动清除缓存。

缓存的范围分为为是SESSION和和STATEMENT两种,默认是是SESSION,可以将缓存范围设置STATEMENT,mybatis-cofig.xml配置如下:

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

也可以设置映射器的的flushCache="true"来实现清除缓存 ,不过这设置只在当前映射配置有效。

<select id="selectUserById" resultMap="baseMap" flushCache="true"> 
    select * from user where id = #{id}
</select>
二级缓存

一级缓存是sqlsession对象级别的,而二级缓存是 SqlSessionFactory 级别,在整个应用都有效,可以在多个会话有效,本质上是将缓存的作用域由sqlsession扩大到SqlSessionFactory级别。

注:二级缓存默认是关闭的,需要自己开启。

开启二级缓存:

1、在核心配置文件mybatis-cofig.xml配置

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

注:其实cacheEnabled的默认值为true。

2、在UserMapper.xml开启二级缓存

<cache />  <!-- 开户二级缓存-->

3、对应的实体类要实现序列接口

public class User implements Serializable {
....
}

xlHHUK.png

测试类代码如下:

@Test
public void cacheTwoTest(){
    User user = userMapper.selectUserById(1);
    System.out.println("第一次查询user = " + user);
    User user1 = userMapper.selectUserById(1);
    System.out.println("第二次查询user1 = " + user1);
    sqlSession.close();
    SqlSession sqlSession = MyBatisUtils.getSqlSession(false);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user2 = mapper.selectUserById(1);
    System.out.println("第三次user2 = " + user2);
}

运行结果:

xlqgl4.png

可见第三次查询也是从缓存中获取。

在UserMapper.xml配置了只是该mapper有效,如果要与其他mapper共享,其他mapper.xml要做如下配置

<cache-ref namespace="com.lwg.dao.UserMapper"/>

在mapper.xml某个sql映射禁用二级缓存,配置userCache属性即可。

<select id="selectUserById" parameterType="int" resultMap="userMap" useCache="false" >
    select * from USER where user_id=#{userId}
</select>

注解开启二级缓存,只需要Mapper类上使用@CacheNamespace
或者 @CacheNameSpaceRef 即可。UserMapper代码如下所示:

注:注解和xml不能同时使用,使用注解要删除xml配置。

x1PZfs.png

二级缓存原理

缓存机制原理如下:

x1PQmT.png

二级缓存原理:

通过xml配置或使用注解@CacheNamespace开启二级缓存,每个mapper.xml或者mapper.class会单独创建一个缓存Map对象缓存数据,多个sqlsession可以共享这些缓存数据,每次执行查询都先查二级缓存,如果二级缓存 不存在就查一级缓存,如果一级缓存不存在就再查数据库,并且将查到的结果缓存在一级缓存和二级缓存中去。

二级缓存和一级缓存一样本质都是一个map缓存数据,但是二级缓存还扩展了相对应的功能,这些功能也可以自己通过XML或者注解进行配置。底层也是通过装饰者设计模式来扩展功能的。

不同的Executor导致不同的功能:
一级缓存 BaseExecutor
二级缓存 CachingExecutor

二级缓存核心功能是对缓存中的数据进行读写,同时二级缓存也提供了附加的功能,例如防止缓存击穿,添加淘汰策略,序列化功能,日志能力,定时清空。

每个功能都对应着如下类的源码:

  • FifoCache:先进先出缓存淘汰策略
  • LRUCache:最近最少使用缓存淘汰策略
  • LoggingCache:具备日志能力的缓存,可以输出缓存命中率。
  • SecheduledCache:定时清空缓存
  • BlockingCache:阻塞式缓存。底层采用分段锁机制,可以控制访问数据库查询数据的线程个数。
  • SeriallizedCache:序列化能力的缓存
  • SynchronizedCache:同步控制的缓存
二级缓存配置

xml配置:

<cache blocking="true" eviction="LRU" flushInterval="86400000" readOnly="true" size="1024" type="org.mybatis.caches.ehcache.EhcacheCache" />

注解配置:

@CacheNamespace(blocking = true, eviction = LruCache.class, flushInterval = 600000L, readWrite = true, size = 300, implementation = EhcacheCache.class)

相关属性取值:

  • blocking:阻塞式缓存,取值为布尔类型
默认为false,当指定为true是采用的是BlockingCache进行封装,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据。简单理解就是再进行并发查询时,只会有一条线程去数据库查询。可以防止缓存雪崩和数据一致性问题。
  • eviction:缓存淘汰策略,默认是LRU,最近最少使用淘汰策略,取值有:FIFO,LRU,SOFT,WEAK。
eviction表示驱逐的意思,也就是缓存驱逐算法,默认是LRU,对应的类是LruCache,默认缓存保留数目为1024个,如果超出则默认按照最少最近使用算法进行驱逐,详情参照LruCache的源码,如果向指定自己的算法,则可以将该值指定为自己的驱逐算法实现类,只需要自己的类实现MyBatis的Cache接口即可。除了LRU以外,系统还提供了FIFO(先进先出,对应FifoCache类),SOFT(采用软引用存储Value,便于垃圾回收,对应SoftCache)和Weak(采用弱引用存储Value,便于垃圾回收,对应WeakCache)这三种策略。
  • flushInterval:缓存刷新间隔
清空缓存的时间间隔,单位是毫秒,默认清空时间是1小时。当指定了该值,则会在每次对应缓存进行操作时判断距离最近一次清空缓存的时间是否超过了flushInterval指定的时间,如果超出了,则清空当前的缓存,详情可参考ScheduleCache的实现。
  • readOnly:只读
默认为false

当指定为false的时候,底层会用SerializedCache包装一次,这种方式会在写缓存的时候将缓存序列化到内存或者本地,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读到的数据也不会影响原来的缓存对象,即非只读,你每次拿到这个缓存结果都可以进行修改,而不会影响缓存的数据。

当指定为true时,那就是每次获取的都是同一个引用,对其进行修改会影响后续的缓存数据的获取,这种情况下是不建议对获取到的数据进行更改,意为只读(不建议设置为true)

这是MyBatis二级缓存读写和只读的定义,可能与我们通常情况下的只读和读写意义不同注意每次进行序列化和反序列化无疑会影响性能,但是这样的缓存结果更安全,不会随意更改,具体可根据实际情况进行选择,详情可以参考SerializedCache的源码。
  • size:缓存对象数量
用来指定缓存中最多保存的Key的数量,其实是针对LruCache而言的,LruCache默认只存1024个对象,可通过该属性来改变默认值。
  • type:缓存类型,可以集成第三方缓存,如如Ecache。
type属性用来指定当前底层缓存的实现类,默认是PerpetualCache,如果我们想自定义则可以通过该属性来指定,对应的值是我们自定义的Cache的全路径名称。

二级缓存默认开启了 同步,LRU,序列化,Log功能。

MyBatis集成第三方缓存框架EhCache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点, 是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,JavaEE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序。

主要特性:

  1. 快速
  2. 简单
  3. 支持多种缓存策略
  4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题
  5. 缓存数据会在虚拟机重启的过程中写入磁盘
  6. 可以通过RMI、可插入API等方式进行分布式缓存
  7. 具有缓存和缓存管理器的侦听接口
  8. 支持多缓存管理器实例,以及一个实例的多个缓存区域
  9. 提供Hibernate的缓存实现
  10. MyBatis也可以自己来集成EhCache缓存框架
EhCache的基本使用

1、导依赖

<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>

2、创建建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 path="D:\ehcache"/>
<!--    默认缓存-->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            memoryStoreEvictionPolicy="LRU"/>

    <!-- 持久化缓存 -->
    <!-- <defaultCache
        maxElementsInMemory="1"
        eternal="true" overflowToDisk="true"
        maxElementsOnDisk="0"
        diskPersistent="true"/> -->

    <!-- 指定名称缓存 -->
    <cache name="lwg"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="5"
           timeToLiveSeconds="5"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>

</ehcache>
常用配置

常见的配置项:

  • diskStore:设置磁盘缓存位置。
  • defaultCache:默认的管理策略,如果不加特殊设置,则所有对象都按此配置处理。
  • cache:指定的管理策略,会继承默认的管理策略,也可以自定义,如果和默认的策略重复会自动覆盖。

常用属性:

  • name:缓存名字
  • maxElementsInMemory:内存中最大缓存对象数
  • maxElementsOnDisk:硬盘中最大缓存对象数,0表示无穷大
  • overflowToDisk:true表示内存缓存到达maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存对象写到硬盘里,这个对象必须实现Serializable接口。
  • diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB,每个Cache都应该有自己的一个缓存区。
  • maxEntriesLocalHeap:内存大小,单位是字节。
  • maxEntriesLocalDisk:缓存文件大小,单位是字节。
  • eternal: 默认为false,true表示对象永不过期,此时会忽略。
  • timeToIdleSeconds和timeToLiveSeconds

​ timeToIdleSeconds:设定允许对象处于空闲时间状态的最长时间,单位是秒,当对象从最近一次被访问后,如果处于空闲时间超过timeToIdleSeconds时间后,这个对象就会过期,EhCache就会将它从缓存里清除,只有eternal为false才生效。如果该属性设为0,则表示对象可以无限期的处于空闲状态。

​ timeToLiveSeconds:设定对象允许在缓存的最大时间,单位是秒,如果对象在缓存的时间超过timeToLiveSeconds设定的时间,该对象就会过期EhCache就会将它从缓存中清除,只有eternal为false才生效。如果该属性设为0,则表示该对象可以无限期存于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds才有意义。

  • diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认是120秒。
  • diskPersistent:是否缓存虚拟机重启期数据 。
  • memoryStoreEvictionPolicy:淘汰策略,当达到maxElementsInMemory限制时,EhCache将会根据指定的策略去清理内存。默认的策略是LRU(最近最少使用)。还可以设置FIFO(先进先出)和LFU(较少使用),但没有提供用户自定义接口,只可以设置这三种策略。

FIFO:first in first out (先进先出) 。
LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存 。
LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。

EaCache实现持久化缓存

Ehcache默认配置是为了提高效率,所以有一部分缓存是在内存 中,如果达到配置的内存对象总量,则才根据策略持久化到硬盘中,这里是有一个问题的,假如系统突然中断运行那内存中的那些缓存,直接被释放掉了,不能持久化到硬盘;这种数据丢失,对于一般项目是不会有影响的,但是在某些情况下不希望数据丢失,那么我们可以通过配置跳过内存缓存,直接不经过内存,直接缓存到文件中。 这时候我们就需要通过Ehcache配置,来实现缓存的持久化,不存内存中。这时候我们就需要通过Ehcache配置,来实现缓存的持久化,不存内存中。

<?xml version="1.0" encoding="UTF‐8"?> 
<ehcache> 
    <!‐‐ 磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存 path:指定在硬盘上存储对象的路径 ‐‐> 
	<diskStore path="C:\ehcache" /> 
    <defaultCache maxElementsInMemory="100" eternal="true" overflowToDisk="true"/> 
<!--    maxElementsInMemory设置成1,overflowToDisk设置成true, 只要有一个缓存元素,就直接存到硬盘上去 eternal设置成true,代表对象永久有效 maxElementsOnDisk设置成0 表示硬盘中最大缓存对象数无限大 diskPersistent设置成true表示缓存虚拟机重启期数据 				-->
    <cache name="lwg"
           maxElementsInMemory="1"
           eternal="true"
           overflowToDisk="true"
           maxElementsOnDisk="0"
           diskPersistent="true"/>

</ehcache
MyBatis整合EaCahe实现二级缓存

二级缓存是 SqlSessionFactory级别,在整个应用都有效,可以在多个会话有效。mybaits的二级缓存是mapper范围级别。MyBatis集成第三方缓存框架也是需要开启缓存的,步骤如下所示:

1、导EaCahe依赖

<!-- ehcache依赖 -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>
<!--mybatis与ehcache整合-->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.1.0</version>
        </dependency>

2、在mybatis-config.xml开启设置二级缓存

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

3、在对应的mapper开启二级缓存,并设置二级缓存类型为EhCache。

<cache  type="org.mybatis.caches.ehcache.EhcacheCache"  />

4、创建eacache.xml配置文件,并进行相应的配置。

5、编写测试类代码。

@Test
public void cacheTwoTest(){
    User user = userMapper.selectUserById(1);
    System.out.println("第一次查询user = " + user);
    User user1 = userMapper.selectUserById(1);
    System.out.println("第二次查询user1 = " + user1);
    sqlSession.close();
    SqlSession sqlSession = MyBatisUtils.getSqlSession(false);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user2 = mapper.selectUserById(1);
    System.out.println("第三次user2 = " + user2);
}

MyBatis缓存原理总结

x184bD.png

原理:

  1. 客户端执行查询从数据库获取数据,建立一次会话,即SqlSession对象。
  2. 执行查询时候,先访问CachingExcutor对象,CachingExcutor先从二级缓存中查找数据,如果有就直接返回,如果没有,就进入BaseExcutor执行一级缓存,如果还是没有就直接查询数据库中的数据,并且会将查询结果保存到缓存中,同一个SqlSession中再次访问就可以从一级缓存中获取了。
  3. 由于MyBatis的缓存只用了map来实现,所以如果想要永久缓存,那么MyBatis开放了接口集成第三方缓存框架,只需要实现Cache接口即可,MyBatis可以整合第三方框架,以后就可以交给第三方框架来管理缓存对象了。

MyBatis分页

mybatis在内存中进行数据分页依赖的是 RowBounds 类。

实现分页步骤:

1、编写查询所有的接口方法,形参为RowBounds类对象。

public List<User> selectUserAllPage(RowBounds rowBounds);

2、编写查询所有的sql语句

<select id="selectUserAllPage" resultMap="userMap">
    select * from USER
</select>

3、编写测试类

 @Test
    public void selectUserAllPageTest(){
//        RowBounds传入两个参数,一个是起始行,一个是当前页显示多少条行数
        List<User> users = userMapper.selectUserAllPage(new RowBounds(1,2));
        System.out.println("users = " + users);
    }

执行结果是查询了所有的数据,但是由于MyBatis对数据进行了内存分页,所以实际查询到的数据是从第二条开始,一共查询了2条记录。

或者也可以使用RowBounds.DEFAULT,不分页,查询所有。如:

@Test
public void selectUserAllPageTest(){
    List<User> users = userMapper.selectUserAllPage(RowBounds.DEFAULT);
    System.out.println("users = " + users);
}

MyBatis分页插件

MyBatis分页插件就是用 pagehelper 插件,实现步骤:

1、导入对应的依赖

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.1</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId> 
    <artifactId>jsqlparser</artifactId>
    <version>4.2</version>
</dependency>

2、mybatis-config.xml导入插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

注:插件本质是一个拦截器。

3、编写接口查询所有方法以及查询语句。

public List<User> selectUserAllPagePlug();
<select id="selectUserAllPagePlug" resultMap="userMap">
    select * from USER
</select>

4、编写测试类

@Test
public void selectUserAllPagePlugTest(){
    PageHelper.startPage(2,3);
    List<User> users = userMapper.selectUserAllPagePlug();
    System.out.println("users = " + users);
}
分页插件的使用

1、分布查询sql语句 select * from user limt (currentPage - 1) * pageSize, pageSize 。其中currentPage表示当前页数是第几页,pageSize每页显示的条数。

2、在执行查询方法之前,使用用 PageHelper.startPage(int currentPage , int pageSize) 开启分页功能。

3、PageHelper.startPage(2,3)方法有返回值的,查询返回对象里面包含了所有的分页相关信息。

users = Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=12, pages=4, reasonable=false, pageSizeZero=false}[User{userId=11, userName=‘王华’, password=‘23455’, nature=‘4’, hobbies=null}, User{userId=12, userName=‘又又’, password=‘235322455’, nature=‘1’, hobbies=null}, User{userId=13, userName=‘华子’, password=‘0976’, nature=‘1’, hobbies=null}]

4、使用PageInfo pageInfo=new PageInfo<>(users);可以获取更详细的信息,代码如下:

@Test
public void selectUserAllPagePlugTest(){
    PageHelper.startPage(2, 3);
    List<User> users = userMapper.selectUserAllPagePlug();
    PageInfo<User> pageInfo=new PageInfo<>(users);
    System.out.println("users = " + users);
    System.out.println("pageInfo = " + pageInfo);
}

输出信息

pageInfo = PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=12, pages=4, list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=12, pages=4, reasonable=false, pageSizeZero=false}[User{userId=11, userName=‘王华’, password=‘23455’, nature=‘4’, hobbies=null}, User{userId=12, userName=‘又又’, password=‘235322455’, nature=‘1’, hobbies=null}, User{userId=13, userName=‘华子’, password=‘0976’, nature=‘1’, hobbies=null}], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=8, navigateFirstPage=1, navigateLastPage=4, navigatepageNums=[1, 2, 3, 4]}

分页插件输出数据:

  • pageNum:当前页的页码
  • pageSize:当前页显示的记录数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页数
  • list:分页后的数据集合
  • prePage:上一页页码
  • nextPage:下一页页码
  • isFirstPage:是否是第一页
  • isLastPage:是否是最后一页
  • hasPreviousPage:是否有上一页
  • hasNextPage:是否有下一页
  • navigatePages:分页导航显示的数量
  • navigateFirstPage:分页导航显示的第一个页码
  • navigateLastPage:分页导航显示的最后一个页码
  • navigatepageNums:分页导航显示的所有页码

MyBatis逆向工程

正向工程:根据创建的Java实体类,由框架负责通过实体类信息生成数据库的表。

逆向工程:根据创建的数据库表,由框架负责根据数据库表,反向生成Java相关代
码,相关信息如下所示:

  • Java实体类
  • Mapper接口
  • Mapper映射配置文件

原理:反射扫描实体类,以及实体的所有属性,动态拼接建表SQL。

d selectUserAllPageTest(){
List users = userMapper.selectUserAllPage(RowBounds.DEFAULT);
System.out.println("users = " + users);
}


### MyBatis分页插件

MyBatis分页插件就是用 pagehelper 插件,实现步骤:

1、导入对应的依赖

```xml
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.1</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId> 
    <artifactId>jsqlparser</artifactId>
    <version>4.2</version>
</dependency>

2、mybatis-config.xml导入插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

注:插件本质是一个拦截器。

3、编写接口查询所有方法以及查询语句。

public List<User> selectUserAllPagePlug();
<select id="selectUserAllPagePlug" resultMap="userMap">
    select * from USER
</select>

4、编写测试类

@Test
public void selectUserAllPagePlugTest(){
    PageHelper.startPage(2,3);
    List<User> users = userMapper.selectUserAllPagePlug();
    System.out.println("users = " + users);
}
分页插件的使用

1、分布查询sql语句 select * from user limt (currentPage - 1) * pageSize, pageSize 。其中currentPage表示当前页数是第几页,pageSize每页显示的条数。

2、在执行查询方法之前,使用用 PageHelper.startPage(int currentPage , int pageSize) 开启分页功能。

3、PageHelper.startPage(2,3)方法有返回值的,查询返回对象里面包含了所有的分页相关信息。

users = Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=12, pages=4, reasonable=false, pageSizeZero=false}[User{userId=11, userName=‘王华’, password=‘23455’, nature=‘4’, hobbies=null}, User{userId=12, userName=‘又又’, password=‘235322455’, nature=‘1’, hobbies=null}, User{userId=13, userName=‘华子’, password=‘0976’, nature=‘1’, hobbies=null}]

4、使用PageInfo pageInfo=new PageInfo<>(users);可以获取更详细的信息,代码如下:

@Test
public void selectUserAllPagePlugTest(){
    PageHelper.startPage(2, 3);
    List<User> users = userMapper.selectUserAllPagePlug();
    PageInfo<User> pageInfo=new PageInfo<>(users);
    System.out.println("users = " + users);
    System.out.println("pageInfo = " + pageInfo);
}

输出信息

pageInfo = PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=12, pages=4, list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=12, pages=4, reasonable=false, pageSizeZero=false}[User{userId=11, userName=‘王华’, password=‘23455’, nature=‘4’, hobbies=null}, User{userId=12, userName=‘又又’, password=‘235322455’, nature=‘1’, hobbies=null}, User{userId=13, userName=‘华子’, password=‘0976’, nature=‘1’, hobbies=null}], prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=8, navigateFirstPage=1, navigateLastPage=4, navigatepageNums=[1, 2, 3, 4]}

分页插件输出数据:

  • pageNum:当前页的页码
  • pageSize:当前页显示的记录数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页数
  • list:分页后的数据集合
  • prePage:上一页页码
  • nextPage:下一页页码
  • isFirstPage:是否是第一页
  • isLastPage:是否是最后一页
  • hasPreviousPage:是否有上一页
  • hasNextPage:是否有下一页
  • navigatePages:分页导航显示的数量
  • navigateFirstPage:分页导航显示的第一个页码
  • navigateLastPage:分页导航显示的最后一个页码
  • navigatepageNums:分页导航显示的所有页码

MyBatis逆向工程

正向工程:根据创建的Java实体类,由框架负责通过实体类信息生成数据库的表。

逆向工程:根据创建的数据库表,由框架负责根据数据库表,反向生成Java相关代
码,相关信息如下所示:

  • Java实体类
  • Mapper接口
  • Mapper映射配置文件

原理:反射扫描实体类,以及实体的所有属性,动态拼接建表SQL。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广深度优先

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值