简易实现 MyBatis 底层机制

MyBatis

大家好呀!我是小笙,我中间有1年没有更新文章了,主要忙于毕业和就业相关事情,接下来,我会恢复更新!我们一起努力吧!


概述

MyBatis 是一个持久层的框架(前身是 ibatis,在 ibatis3.x 的时候更名为 MyBatis)

为什么使用 MyBatis 框架?
  1. 简化以及规范化连接数据库(不需要手动连接数据库)
  2. 程序不再是以一种硬编码的形式实现(解耦,灵活性高)
MyBatis 核心框架图

简易实现 MyBatis 底层机制

备注:简易实现MyBatis底层机制所用到的表如上述快速入门,实现的是简单查询用户数据

实现框架图
SqlSession 类的设计

SqlSession:主要是用来连接数据库以及操作数据库

java 复制代码
public class SqlSession {
    /**
     * 主要用来连接数据库
     */
    private Configuration configuration = new Configuration();
    /**
     * 主要用来操作数据库
     */
    private ExecutorImpl executor = new ExecutorImpl();

    /**
     * 直接操作数据库来查询用户数据
     */
    public <T> T selectOne(String statement, Object parameter) {
        try {
            return executor.selectOne(statement, parameter);
        } catch (SQLException throwables) {
            throw new RuntimeException();
        }
    }

    /**
     * 返回mapper的动态代理对象
     */
    public <T> T getMapper(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
                new MapperProxy(configuration,this,"repo/UserMapper.xml"));
    }
}

Configuration 类主要是用来读取配置文件

  • 读取config数据库配置文件,获取连接
  • 读取mapper接口以及mapper接口对应的xml配置文件信息,封装到MapperBean对象中
java 复制代码
/**
 * 配置读取类
 */
public class Configuration {
    // 日志输出
    private static Logger log = Logger.getLogger("com.Altair.myBatis.logs");;
    // 获取资源加载类
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();

    /**
     * 读取配置文件
     * @param configName 配置文件名字
     * @return
     */
    public Connection ReadConfig(String configName) {
        Connection connection = null;
        try {
            // 获取 xml 配置文件流
            InputStream stream = loader.getResourceAsStream(configName);
            // SAXReader 读取 xml 配置文件内容
            SAXReader read = new SAXReader();
            Document document = read.read(stream);
            // 获取 config 根目节点
            Element rootElement = document.getRootElement();
            if(Constants.DATABASE_CONFIG.equals(rootElement.getName())){
                String driver = "";
                String url = "";
                String username = "";
                String password = "";
                for (Object o : rootElement.elements(Constants.DATABASE_PROPERTY)) {
                    Element element = (Element) o;
                    String name = element.attributeValue("name");
                    String value = element.attributeValue("value");

                    if(name == null || value == null){
                       log.info("异常报错信息:数据库配置参数错误或者不全!");
                        throw  new RuntimeException("异常报错信息:数据库配置参数错误或者不全!");
                    }
                    switch (name){
                        case Constants.DATABASE_PARAM.DRIVER:
                            driver = value;break;
                        case Constants.DATABASE_PARAM.URL:
                            url = value;break;
                        case Constants.DATABASE_PARAM.USERNAME:
                            username = value;break;
                        case Constants.DATABASE_PARAM.PASSWORD:
                            password = value;break;
                        default: break;
                    }
                }
                connection =  getConnection(driver,url,username,password);
            }
        }catch (Exception e){
            log.info("异常报错信息:" + e.getMessage());
            throw  new RuntimeException("异常报错信息:" +  e.getMessage());
        }
        return connection;
    }

    /**
     * 获取数据库连接
     */
    private Connection getConnection(String driver,String url,String username,String password){
        if(driver == "" || url == "" || username == "" || password == "") {
            log.info("异常报错信息:数据库配置参数不全!");
            throw new RuntimeException("异常报错信息:数据库配置参数不全!");
        }
        try {
            Class.forName(driver);
            return DriverManager.getConnection(url, username, password);
        }catch (Exception e){
            log.info("异常报错信息:数据库连接异常!");
            throw new RuntimeException("异常报错信息:数据库连接异常!");
        }
    }

    /**
     * 读取Mapper.xml文件生成MapperBean对象
     * @param filePath xml文件路径
     * @return MapperBean对象
     */
    public MapperBean readMapper(String filePath){
        MapperBean mapperBean = new MapperBean();
        // 获取文件流
        InputStream stream = loader.getResourceAsStream(filePath);
        // SAXReader 读取 xml 配置文件内容
        SAXReader saxReader = new SAXReader();
        Document doc = null;
        try {
            doc = saxReader.read(stream);
        } catch (DocumentException e) {
            throw new RuntimeException("读取xml配置文件失败!");
        }
        // 获取 mapper.xml 根目节点
        Element root = doc.getRootElement();
        // 对应Mapper接口路径
        String namespace = root.attributeValue("namespace").trim();
        mapperBean.setInterfacePath(namespace);
        // 获取迭代器-遍历所有节点
        Iterator iterator = root.elementIterator();
        List<Function> funs = new ArrayList<>();
        while(iterator.hasNext()){
            Element node = (Element)iterator.next();
            Function function = new Function();
            function.setSqlType(node.getName());
            function.setSql(node.getTextTrim());
            function.setFuncName(node.attributeValue("id").trim());
            String parameterType = node.attributeValue("parameterType");
            if(parameterType != null){
                Class<?> aClass = null;
                try {
                    aClass = Class.forName(parameterType.trim());
                    function.setParamster(aClass.newInstance());
                } catch (Exception e) {
                    throw new RuntimeException("传入参数对象异常!"+ e.getMessage());
                }
            }
            String resultType = node.attributeValue("resultType");
            if(resultType != null){
                Class<?> aClass = null;
                try {
                    aClass = Class.forName(resultType.trim());
                    function.setResultType(aClass.newInstance());
                } catch (Exception e) {
                    throw new RuntimeException("返回参数对象异常!" + e.getMessage());
                }
            }
            funs.add(function);

        }
        mapperBean.setFunctions(funs);
        System.out.println(mapperBean);
        return mapperBean;
    }
}
MapperBean 类的设计

MapperBean 主要用于封装 Mapper接口以及对应xml文件的信息

java 复制代码
/**
 * 用来连接Mapper接口和Mapper.xml文件
 */
@Data
public class MapperBean {
    /**
     * 接口全路径
     */
    private String interfacePath;
    /**
     * 接口的方法信息
     */
    private List<Function> functions;
}
java 复制代码
/**
 * 用来记录 Mapper 方法信息
 */
@Data
public class Function {
    /**
     * sql类型
     */
    private String SqlType;
    /**
     * sql语句
     */
    private String sql;
    /**
     * 方法名
     */
    private String funcName;
    /**
     * 参数类型
     */
        private Object paramster;
    /**
     * 返回类型
     */
    private Object resultType;
}
MapperProxy 代理对象的设计
java 复制代码
/**
 * 简易的代理对象
 */
public class MapperProxy implements InvocationHandler {

    private Configuration configuration = null;
    private SqlSession sqlSession = null;
    private String filePath = "";

    public MapperProxy(Configuration configuration, SqlSession sqlSession, String filePath) {
        this.configuration = configuration;
        this.sqlSession = sqlSession;
        this.filePath = filePath;
    }

    /**
     * 返回代理对象
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MapperBean mapperBean = configuration.readMapper(this.filePath);

        //判断是否是xml文件对应的接口
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfacePath())) {
            return null;
        }

        // 取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        // 判断当前mapperBean解析对应MappperXML后 , 有方法
        if (null != functions && 0 != functions.size()) {
            for (Function function : functions) {
                // 当前要执行的方法和function.getFuncName()一样
                // 说明我们可以从当前遍历的function对象中,取出相应的信息sql, 并执行方法
                if(method.getName().equals(function.getFuncName())) {
                    if("select".equalsIgnoreCase(function.getSqlType())) {
                        return sqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
                    }
                }
            }
        }
        return null;
    }
}
测试类
java 复制代码
/**
 * 测试用例
 */
public class TestReadMapper {
    @Test
    public void test(){
        SqlSession sqlSession = SessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(53);
        System.out.println("user:" + user);
    }
}

快速入门

概述:创建、操作 User 用户,也就是俗称的 CRUD,简单上手,了解大致的开发流程

基础环境
引入依赖包
  • 引入jar包形式: mybatis jar包地址

  • Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

    xml 复制代码
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.15</version>
    </dependency>
创建数据库表和对象

首先创建一个 User 表

sql 复制代码
create table user
(
    id    int auto_increment primary key,
    name  varchar(64)   not null default '',
    age   int           not null default '',
    email varchar(255)  not null default ''
);

User 实体类

java 复制代码
@Data
public class User {
    private long id;
    private int age;
    private String name;
    private String email;
}
配置连接数据库信息
xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置日志输出(可以输出运行了的MySQL语句) -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <!-- 配置别名 -->
    <typeAliases>
        <typeAlias alias="User" type="com.Al_tair.entity.User"/>
        <typeAlias alias="String" type="java.lang.String"/>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="List" type="java.util.List"/>
    </typeAliases>

	<!-- 配置数据库 -->
    <environments default="development">
        <!-- 配置实物管理器 -->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <!-- 配置驱动 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- 配置连接数据库 -->
                <property name="url" value="xxxxxx"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxxxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="./repo/UserMapper.xml"/>
         <!-- 使用注解的形式 -->
    	<mapper class="com.Al_tair.mapper.UserAnnotationMapper"></mapper>
    </mappers>
</configuration>
配置xml文件方式(推荐)

UserMapper 接口

java 复制代码
public interface UserMapper {
    /**
     * 添加用户
     * @param user 用户信息
     */
    public void addUser(User user);
    /**
     * 删除用户
     * @param id 用户id
     */
    public void deleteUser(Integer id);
    /**
     * 更新用户
     * @param user 用户信息
     */
    public void updateUser(User user);
    /**
     * 查询用户
     * @param id 用户id
     */
    public User findUserById(Integer id);
    /**
     * 查询用户集合
     * @param ids 用户id
     */
    public List<User> findUsers(Integer[] ids);
}

UserMapper 接口对应 UserMapper.xml 文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace: 指定对应的接口 -->
<mapper namespace="com.Al_tair.mapper.UserMapper">
    <!-- id:指定接口对应的方法名 -->
    <!-- parameterType:入参数据类型 -->
    <insert id="addUser" parameterType="User">
        insert into user (name, age, email)
                VALUES (#{name},#{age},#{email});
    </insert>

    <delete id="deleteUser" parameterType="Integer">
        delete from user where id = #{id};
    </delete>

    <update id="updateUser" parameterType="User">
        update user set age = #{age},name = #{name},email = #{email} where id = #{id};
    </update>

    <select id="findUserById" parameterType="Integer" resultType="User">
        select * from user where id = #{id};
    </select>
</mapper>

测试类

java 复制代码
public class quickTest {
    private SqlSession sqlSession;
    private UserMapper mapper;
    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        mapper = sqlSession.getMapper(UserMapper.class);
    }
    /**
     * 添加用户
     */
    @Test
    public void test1(){
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setName("lns" + i * 2);
            user.setAge(i + 10);
            user.setEmail("1045645.qq.com");
            mapper.addUser(user);
        }
        if(sqlSession != null){
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("添加成功...");
    }
    /**
     * 删除用户
     */
    @Test
    public void test2(){
        mapper.deleteUser(44);
        mapper.deleteUser(45);
        mapper.deleteUser(46);
        if(sqlSession != null){
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("删除成功...");
    }
    /**
     * 修改用户
     */
    @Test
    public void test3(){
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(47+i);
            user.setName("zlr" + i * 2);
            user.setAge(i + 100);
            user.setEmail("1045645.google.com");
            mapper.updateUser(user);
        }
        if(sqlSession != null){
            sqlSession.commit();
            sqlSession.close();
        }
        System.out.println("修改成功...");
    }
    /**
     * 查询一条用户数据
     */
    @Test
    public void test4(){
        User user = mapper.findUserById(53);
        if(sqlSession != null){
            sqlSession.close();
        }
        System.out.println("查询成功,数据:" + user);
    }
}
使用注解方式

配置文件需要添加该如下配置

xml 复制代码
<mappers>
    <!-- 使用注解的形式 -->
    <mapper class="com.Al_tair.mapper.UserAnnotationMapper"></mapper>
</mappers>

接口采用注解的形式填入SQL语句

java 复制代码
public interface UserAnnotationMapper {
    /**
     * 添加用户
     *
     * @param user 用户信息
     */
    @Insert("insert into user (name, age, email) VALUES (#{name},#{age},#{email});")
    public void addUser(User user);

    /**
     * 删除用户
     *
     * @param id 用户id
     */
    @Delete("delete from user where id = #{id};")
    public void deleteUser(Integer id);

    /**
     * 更新用户
     *
     * @param user 用户信息
     */
    @Update("update user set age = #{age},name = #{name},email = #{email} where id = #{id};")
    public void updateUser(User user);

    /**
     * 查询用户
     *
     * @param id 用户id
     */
    @Select("select * from user where id = #{id};")
    public User findUserById(Integer id);

    /**
     * 查询用户集合
     *
     * @param ids 用户id
     */
    @Select("select * from user where id in #{id};")
    public List<User> findUsers(Integer[] ids);
}

测试方法

java 复制代码
public class AnnotationTest {
    private SqlSession sqlSession;
    private UserAnnotationMapper mapper;
    @Before
    public void init(){
        sqlSession = MyBatisUtils.getSqlSession();
        mapper = sqlSession.getMapper(UserAnnotationMapper.class);
    }

    /**
     * 测试查询一条用户数据
     */
    @Test
    public void test(){
        User user = mapper.findUserById(53);
        if(sqlSession != null){
            sqlSession.close();
        }
        System.out.println("查询成功,数据:" + user);
    }
}

注解配置方式的总结

java 复制代码
// 增删改查操作
@Insert("SQL语句")
@Delete("SQL语句")
@Update("SQL语句")
@Select("SQL语句")

// id自增长(keyProperty 对应的是对象属性名;keyColumn对应的是表的字段名)
@Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")

映射器

select 元素的属性
属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
insert、update、delete元素的属性
属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache 将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys (仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty (仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
`keyColumn (仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
parameterType(输入参数类型)
  1. 传入简单数据类型

  2. 传入对象数据类型,查询时需要有多个筛选条件

    注意:当传入的参数类是 String 时,也可以使用 ${} 来接收参数

xml 复制代码
<!-- 实现 findUserByNameORId -->
<select id="findUserByNameORId" parameterType="User" resultType="User">
    SELECT * FROM user
    WHERE id=#{id} OR name=#{name}
</select>
<!-- 模糊查询需要 ${value} 取值-->
<select id="findUserByName" parameterType="String" resultType="User">
	SELECT * FROM user
	WHERE name LIKE '%${value}%' 
</select>

注意:上述传入参数之所以能够简写,是需要配置项扫描对应实体类的路径

xml 复制代码
<typeAliases>
    <package name="com.Al_tair.entity"/>
</typeAliases>
传入和返回 HashMap
xml 复制代码
<!--
	#{id}、#{age} 传入的参数对应 map 里面 key,如果不传值默认为null
-->
<select id="findUserByIdAndAge_PrameterHashMap_ReturnHashMap" parameterType="map" resultType="map">
    SELECT id,age FROM user
	WHERE id > #{id} AND age > #{age}
</select>
数据库表的字段名和对象属性名不一致解决方法
  1. 查询出来的字段添加别名来匹配对象属性名

  2. 通过配置 resutlMap 来映射他们之间的关系

    xml 复制代码
    <!--
    	表的字段名: user_id、user_name、user_email
    	对象的属性名:user_id、username、useremail
    -->
    <resultMap type="User" id="findAllUserMap">
        <result column="user_name" property="username"/>
        <result column="user_email" property="useremail"/>
    </resultMap>
    <select id="findAllUser" resultMap="findAllUserMap" >
    	SELECT * FROM user
    </select>

动态SQL语句

动态 SQL 常用标签

if
xml 复制代码
<!-- 如果用户的年龄不大于0则输出所有用户 ; 反之用户输出的年龄大于0,则按照输入的年龄进行过滤 -->
<select id="findUserByAge" parameterType="Integer" resultType="User">
	SELECT * FROM user WHERE 1 = 1
    <!-- test 里面的 age 是来自传入的参数,但是想要取到值,传入的参数需要加上注解 @param(age) -->
    <if test="age >= 0">
        AND age > #{age}
    </if>
</select>
where
xml 复制代码
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeAndName" parameterType="User" resultType="User">
    SELECT * FROM user
    <where>
        <if test = "age >= 0">
            And age >= #{age}
        </if>
        <if test="name != null and name != '' ">
            And name = #{name}
        </if>
    </where>
</select>
choose/when/otherwise
xml 复制代码
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeOrName" parameterType="User" resultType="User">
    SELECT * FROM user WHERE
    <choose>
        <when test="age >= 0">
            age >= #{age}
        </when>
        <otherwise>
            name = #{name}
        </otherwise>
    </choose>
</select>
foreach
xml 复制代码
<!-- 过滤查询用户信息 -->
<select id="findUserByIds" parameterType="Map" resultType="User">
    SELECT * FROM user
    <if test="ids != null and ids != ''">
        WHERE id IN
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </if>
</select>
trim

替换关键字/定制元素的功能(扩展了 where 标签的能力)

xml 复制代码
<!-- 过滤查询用户信息 -->
<select id="findUserByAgeAndName" parameterType="User" resultType="User">
    SELECT * FROM user
    <!-- trim 标签能够加前缀 where 或者去掉对于的 AND|OR|XXX 的前缀 -->
    <trim prefix="WHERE" prefixOverrides="AND|OR|XXX"
        <if test = "age >= 0">
            And age >= #{age}
        </if>
        <if test="name != null and name != '' ">
            And name = #{name}
        </if>
    </trim>
</select>
set

在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值

XML 复制代码
<!-- 更新用户信息 -->
<update id="updateUserInfo" parameterType="Map">
    UPDATE user
    <set>
        <if test="age >= 0">
            age = #{age},
        </if>
        <if test="name != null and name != ''">
            name = #{name},
        </if>
        <if test="email != null and email != ''">
            email = #{email}
        </if>
    </set>
    WHERE id = #{id}
</update>

一对一映射

配置Mapper.xml
java 复制代码
// 用户类
@Setter
@Getter
@ToString
public class User {
    private String id;
    private int age;
    private String name;
    private String email;
    private IdCard idCard;
    private String cardId;
}

// idCard类 
@Setter
@Getter
@ToString
public class IdCard {
    private String id;
    private String userId;
    private String idCard;
}

第一种方法

xml 复制代码
    <!-- 映射返回的结果 -->
    <resultMap id="userResultMap" type="User">
        <!-- 用<id 标记主键会优化性能 -->
        <!-- <result property="id" column="id"/> -->
        <id property="id" column="id"/>
        <result property="age" column="age"/>
        <result property="name" column="name"/>
        <result property="email" column="email"/>
        <association property="idCard" javaType="IdCard">
            <result property="id" column="id"/>
            <result property="userId" column="userId"/>
            <result property="idCard" column="idCard"/>
        </association>
    </resultMap>
	<!-- 查询用户信息 -->
    <select id="getUserById" parameterType="String" resultMap="userResultMap">
        SELECT * FROM user
            LEFT JOIN idCard ON idCard.userId = user.id
            WHERE user.id = #{id}
    </select>

第二种方式

xml 复制代码
<!-- 映射返回的结果 -->
<resultMap id="userResultMap2" type="User">
    <!-- 用<id 标记主键会优化性能 -->
    <id property="id" column="id"/>
    <result property="age" column="age"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <!-- cardId 为查询出来的 IdCard 表的 id 数据-->
    <association property="idCard" column="cardId" select="com.Al_tair.mapper.IdCardMapper.getIdCardById"/>
</resultMap>
<select id="getUserById2" parameterType="String" resultMap="userResultMap2">
    SELECT * FROM user WHERE user.id = #{id}
</select>
注解的方式
java 复制代码
/**
  * 获得idCard
  */
@Select("SELECT * FROM idCard WHERE id = #{id}")
public IdCard findIdCardById(String id);
/**
  * 查询用户
  */
@Select("select * from user where id = #{id}")
@Results({
    @Result(id = true,property = "id",column = "id"),
    @Result(property = "age",column = "age"),
    @Result(property = "name",column = "name"),
    @Result(property = "email",column = "email"),
    @Result(property = "idCard",column = "cardId",
            one = @One(select = "com.Al_tair.mapper.IdCardMapper.findIdCardById"))
})
public User findUser(Integer id);

多对一映射

java 复制代码
/**
 * 用户信息
 */
@Data
public class User {
    private int id;
    private int age;
    private String name;
    private String email;
    private IdCard idCard;
    private int cardId;
    private List<Pet> pets;
}
/**
 * 宠物
 */
@Data
public class Pet {
    private int id;
    private String name;
    private int userId;
}
xml 复制代码
<!-- 查询用户 -->
<resultMap id="userResultMap3" type="User">
    <!-- 用<id 标记主键会优化性能 -->
    <id property="id" column="id"/>
    <result property="age" column="age"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <association property="idCard" column="cardId" select="com.Al_tair.mapper.IdCardMapper.getIdCardById"/>
    <collection property="pets" column="id" select="com.Al_tair.mapper.PetMapper.getPetByid"/>
</resultMap>
<select id="getUserAndPetById" parameterType="String" resultMap="userResultMap3">
    SELECT * FROM user WHERE user.id = #{id}
</select>
<!-- 查询宠物信息 -->
<select id="getPetByid" parameterType="String" resultType="Pet">
    SELECT * FROM pet WHERE pet.user_id = #{id}
</select>

缓存

一级缓存

概念:一级缓存也是本地缓存

目的:通过缓存提高反复查询数据库的数据

测试:通过传入相同、不同的用户id查询相同的用户信息,进行对比发现如下图

一级缓存失效分析

  • 当SqlSession 会话关闭了,一级缓存将会失效
  • 当执行 SqlSession.clearCache() 可以清除一级缓存
  • 当对查询的对象进行了数据库修改,则会导致该对象的缓存失效
二级缓存

二级缓存和一级缓存本质都是为了提高检索效率的技术

配置相关内容

1、配置 cacheEnabled 为true(默认为true)配置在 mybatis-config.xml 文件中

xml 复制代码
<!-- 全局性关闭或开启所有映射器配置文件中已配置的任何缓存(不会关闭一级缓存) -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

2、二级缓存可能使用到了序列化技术,需要在实体类上实现序列化接口(Serializable)

3、配置在 xxxMapper.xml 文件中

xml 复制代码
<!-- 
    配置二级缓存 
    1、eviction 清除缓存的策略,默认LRU
    2、flushInterval 刷新间隔,单位是毫秒
    3、size 引用的数量
    4、readOnly 只是用来查询,可以提高效率
-->
<cache
       eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

测试用例如下

二级缓存类似全局变量,使用不同 sqlSession 对象依旧能获取到缓存数据

java 复制代码
@Test
public void test(){
    sqlSession = MyBatisUtils.getSqlSession();
    mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.findUserById(2);
    if(sqlSession != null){
        sqlSession.close();
    }
	// 使用不同的 sqlSession 对象
    sqlSession = MyBatisUtils.getSqlSession();
    mapper = sqlSession.getMapper(UserMapper.class);
    User user3 = mapper.findUserById(2);
    if(sqlSession != null){
        sqlSession.close();
    }
}

执行顺序

查询数据的顺序: 二级缓存 -> 一级缓存 -> 数据库

二级缓存和一级缓存不会存在相同的数据,因为如果是查询数据库的时候会把数据放到一级缓存里,只有当SqlSession会话结束的时候,才会将一级缓存的数据转移到二级缓存

EnCache 缓存框架

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider

引入依赖 pom.xml

xml 复制代码
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.2</version>
</dependency>
<!-- mybatis 支持 ehcache 缓存 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
    <scope>compile</scope>
</dependency>

添加 ehcache.xml 配置文件

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <!-- 磁盘缓存位置 -->
    <diskStore path="java.io.tmpdir/ehcache" />

    <!-- 默认缓存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>


	<!-- 自定义缓存 -->
    <!--
		name: 缓存名称
		maxElementsInMemory:缓存最大个数
		timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒)(在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间)
		timeToLiveSeconds: 设置对象在失效前允许存活时间(单位:秒)(无论对象访问或是不访问(闲置),这个对象在cache中的最大存活时间)
		overflowToDisk: 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
		diskSpoolBufferSizeMB: 设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
		maxElementsOnDisk: 硬盘最大缓存个数
		memoryStoreEvictionPolicy: 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存(默认策略是最近最少使用的 LRU)
	-->
    <cache name="UserCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="276000" 
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

添加在 xxxMapper.xml 配置文件

xml 复制代码
<!-- 启用 Ehcache 缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
相关推荐
醉颜凉6 分钟前
【NOIP提高组】潜伏者
java·c语言·开发语言·c++·算法
阿维的博客日记10 分钟前
java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程
java·jvm
qiyi.sky11 分钟前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
lapiii35815 分钟前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
RainbowSea17 分钟前
4. Spring Cloud Ribbon 实现“负载均衡”的详细配置说明
java·spring·spring cloud
程序员小明z18 分钟前
基于Java的药店管理系统
java·开发语言·spring boot·毕业设计·毕设
爱敲代码的小冰37 分钟前
spring boot 请求
java·spring boot·后端
Lyqfor1 小时前
云原生学习
java·分布式·学习·阿里云·云原生
程序猿麦小七1 小时前
今天给在家介绍一篇基于jsp的旅游网站设计与实现
java·源码·旅游·景区·酒店
张某布响丸辣1 小时前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle