简易实现 MyBatis 底层机制

MyBatis

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


概述

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

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

image-20230220230405837

简易实现 MyBatis 底层机制

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

实现框架图

image-20230624103011285

SqlSession 类的设计

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

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对象中
/**
 * 配置读取类
 */
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文件的信息

image-20230618105251350

/**
 * 用来连接Mapper接口和Mapper.xml文件
 */
@Data
public class MapperBean {
    /**
     * 接口全路径
     */
    private String interfacePath;
    /**
     * 接口的方法信息
     */
    private List<Function> functions;
}
/**
 * 用来记录 Mapper 方法信息
 */
@Data
public class Function {
    /**
     * sql类型
     */
    private String SqlType;
    /**
     * sql语句
     */
    private String sql;
    /**
     * 方法名
     */
    private String funcName;
    /**
     * 参数类型
     */
        private Object paramster;
    /**
     * 返回类型
     */
    private Object resultType;
}
MapperProxy 代理对象的设计
/**
 * 简易的代理对象
 */
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;
    }
}
测试类
/**
 * 测试用例
 */
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 文件中:

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

首先创建一个 User 表

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 实体类

@Data
public class User {
    private long id;
    private int age;
    private String name;
    private String email;
}
配置连接数据库信息
<?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 接口

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 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>

测试类

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);
    }
}
使用注解方式

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

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

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

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);
}

测试方法

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);
    }
}

注解配置方式的总结

// 增删改查操作
@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。
resultSetTypeFORWARD_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 时,也可以使用 ${} 来接收参数

<!-- 实现 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>

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

<typeAliases>
    <package name="com.Al_tair.entity"/>
</typeAliases>
传入和返回 HashMap
<!--
	#{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 来映射他们之间的关系

    <!--
    	表的字段名: 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
<!-- 如果用户的年龄不大于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
<!-- 过滤查询用户信息 -->
<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
<!-- 过滤查询用户信息 -->
<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
<!-- 过滤查询用户信息 -->
<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 标签的能力)

<!-- 过滤查询用户信息 -->
<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 的,保持原来的值

<!-- 更新用户信息 -->
<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
// 用户类
@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;
}

第一种方法

    <!-- 映射返回的结果 -->
    <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>

第二种方式

<!-- 映射返回的结果 -->
<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>
注解的方式
/**
  * 获得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);

多对一映射

/**
 * 用户信息
 */
@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;
}
<!-- 查询用户 -->
<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>

缓存

一级缓存

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

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

image-20230808194657116

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

image-20230804183049730 image-20230804183140582

一级缓存失效分析

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

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

image-20230808194733698

配置相关内容

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

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

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

3、配置在 xxxMapper.xml 文件中

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

测试用例如下

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

@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();
    }
}
image-20230808200909676

执行顺序

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

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

EnCache 缓存框架

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

引入依赖 pom.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 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 配置文件

<!-- 启用 Ehcache 缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/308557.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分享两个概念:非受检异常和受检异常

分享两个概念&#xff1a;非受检异常和受检异常 愿你的每一天都充满阳光和笑声&#xff0c;愿每一步都是轻松与愉快。在新的旅程中&#xff0c;愿你找到勇气攀登高峰&#xff0c;找到智慧化解困境。 愿你的心中充满温暖和善意&#xff0c;愿你的梦想如彩虹般美丽且真实。愿你发…

win10 - Snipaste截图工具的使用

win10 - Snipaste截图工具的使用 Step 1&#xff1a;下载 下载链接 提取码&#xff1a;wuv2 Step 2&#xff1a;直接解压可用 找到解压好的目录&#xff0c;并双击exe文件即可 Step 3&#xff1a;设置开机启动 在电脑右下角找到snipaste图标&#xff0c;右键&#xff0c;找…

深入理解 Flink(四)Flink Time+WaterMark+Window 深入分析

Flink Window 常见需求背景 需求描述 每隔 5 秒&#xff0c;计算最近 10 秒单词出现的次数 —— 滑动窗口 每隔 5 秒&#xff0c;计算最近 5 秒单词出现的次数 —— 滚动窗口 关于 Flink time 种类 TimeCharacteristic ProcessingTimeIngestionTimeEventTime WindowAssign…

Java实现基于GDAL将单波段影像转为三波段影像-唯一值渲染

在处理遥感影像的渲染时&#xff0c;经常需要处理单波段影像。单波段影像没有任何颜色&#xff0c;只有一个波段的值。渲染时只能采用色带拉伸、离散颜色、唯一值渲染这几种方式。直接将单波段影像转成三波段的影像&#xff0c;并将三个波段转为颜色对应的rgb值&#xff0c;这样…

人工智能在库存管理中的应用

人工智能在库存管理中的应用 目录 人工智能在库存管理中的应用一、什么是库存管理&#xff1f;二、如何利用AI进行智能库存管理&#xff1f;简化整个库存管理流程在仓库中使用基于人工智能的机器人库存管理及配送数据挖掘与处理提供个性化的客户体验 三、利用人工智能改善库存管…

手撕单链表(单向,不循环,不带头结点)的基本操作

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

鹅目标检测数据集VOC格式300张

鹅&#xff0c;一种家禽&#xff0c;以其独特的形态、生活习性和文化象征意义而备受人们喜爱。 鹅属于鸟纲、雁形目、鸭科&#xff0c;是一种大型水禽。它们的身体肥胖&#xff0c;羽毛洁白如雪&#xff0c;嘴部扁平且坚硬&#xff0c;脚部有蹼&#xff0c;适合在水中游动。 …

智能合约笔记

前言&#xff1a; 首先了解下为什么会出现智能合约&#xff0c;打个比方现在有两个人A和B打赌明天会不会下雨&#xff0c;每个人赌注100元&#xff0c;如果第二天下雨则A拿走200元&#xff0c;否则B拿走200元&#xff0c;这样就有一个问题&#xff0c;赌注要到第二天才能见效&…

RK3568驱动指南|第十篇 热插拔-第112章 热插拔简介

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

在IntelliJ IDEA上使用通义灵码(TONGYI Lingma)

参考链接&#xff1a; 通义灵码产品介绍_智能编码助手_AI编程_云效(Apsara Devops)-阿里云帮助中心 【IDEA如何使用通义灵码&#xff1f;】_idea 通义灵码-CSDN博客 1. 简介 1.1 定义 通义灵码&#xff0c;是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff0c;提…

Android开发基础(一)

Android开发基础&#xff08;一&#xff09; 本篇主要是从Android系统架构理解Android开发。 Android系统架构 Android系统的架构采用了分层的架构&#xff0c;共分为五层&#xff0c;从高到低分别是Android应用层&#xff08;System Apps&#xff09;、Android应用框架层&a…

UML期末复习(带习题,选择题,判断题)(持续更新)

UML期末复习 UML简介UML模型图的构成UML事物UML包含4种事物&#xff1a;构件事物&#xff1a; UML模型的静态部分&#xff0c;描述概念或物理元素行为事物&#xff1a;UML模型图的动态部分&#xff0c;描述跨越空间和时间的行为分组事物&#xff1a; UML模型图的组织部分&#…

某查查请求头参数加密分析(含JS加密算法与Python爬虫源码)

文章目录 1. 写在前面2. 请求分析3. 断点分析4. 扣加密JS5. Python爬虫代码实现 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff…

Acrel-5000重点用能单位能耗在线监测系统的实际应用分析-安科瑞 蒋静

摘要&#xff1a;根据《重点用能节能办法》&#xff08;国家发展改革委等第七部委2018年15号令&#xff09;、《重点用能单位能耗在线监测系统推广建设工作方案》&#xff08;发改环资[2017]1711号&#xff09;和《关于加速推进重点用能单位能耗在线监测系统建设的通知》&#…

【踩坑】JDK1.8 AudioSystem 无法关闭流的问题

文章目录 一、前言二、开始狼人杀嫌疑人1&#xff1a;嫌疑人2&#xff1a; 三、复盘Jdk8原生bug解决方法和原理解析 一、前言 做了一个基于文字转语言的小接口&#xff0c;想删除本地wav文件来着&#xff0c;结果发现删除不了。 很明显被占用了&#xff0c;还是被Java占用了……

点击出现视频弹框

<VideoPlayer ref"video":size"{ width: 88%, height: 100% }" :videoSrc"currentVideo.url"></VideoPlayer>import VideoPlayer from /components/video-player.vue

MySQL之导入以及导出远程备份v

目录 一.navact数据导入导出 1.1 导入 1.2 导出 二. mysqldump命令导入导出数据 2.1 导入 2.2 导出 三.load data file进行数据导入导出&#xff08;只限于单表&#xff09; 3.1 导入 3.2 导出 四.远程连接 好啦就到这里了哦!!!希望帮到你哦!!! 一.navact数据导入导…

条款21:必须返回对象时,别妄想返回其引用

考虑一个表示有理数的类&#xff0c;其中包含一个计算两个有理数相乘的函数: class Rational { public:Rational(int numerator 0, int denominator 1) :n{ numerator }, d{ denominator }{} private:int n, d; // 分子和分母friend const Rational& operator*(const R…

Win11安装与卸载Oracle 19c数据库

一、官网下载安装包 进入官网&#xff0c;选择产品-Oracle DataBase&#xff0c;点击进入下载界面 官网 二、安装 将下载的压缩包进行解压&#xff0c;解压路径随意即可 1 双击exe文件开始安装 等待出现如下页面 2 选择所示&#xff0c;点击下一步 3 选择桌面类安装 4 创…

2000-2022各省、地级市风险投资(VC)数据

2000-2022各省、地级市风险投资&#xff08;VC&#xff09;数据 1、时间&#xff1a;2000-2022年 2、范围&#xff1a;350个地级市&#xff0c;34省 3、指标&#xff1a;包含投资机构层面的风险投资原始数据&#xff0c;汇总到省市层面的结果数据&#xff0c;具体指标如下&a…