当前位置: 首页 > news >正文

简易实现 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;}/*** 返回代理对象*/@Overridepublic 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的functionsList<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 {@Testpublic 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 configurationPUBLIC "-//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 mapperPUBLIC "-//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;@Beforepublic void init(){sqlSession = MyBatisUtils.getSqlSession();mapper = sqlSession.getMapper(UserMapper.class);}/*** 添加用户*/@Testpublic 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("添加成功...");}/*** 删除用户*/@Testpublic void test2(){mapper.deleteUser(44);mapper.deleteUser(45);mapper.deleteUser(46);if(sqlSession != null){sqlSession.commit();sqlSession.close();}System.out.println("删除成功...");}/*** 修改用户*/@Testpublic 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("修改成功...");}/*** 查询一条用户数据*/@Testpublic 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;@Beforepublic void init(){sqlSession = MyBatisUtils.getSqlSession();mapper = sqlSession.getMapper(UserAnnotationMapper.class);}/*** 测试查询一条用户数据*/@Testpublic 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 userWHERE id=#{id} OR name=#{name}
</select>
<!-- 模糊查询需要 ${value} 取值-->
<select id="findUserByName" parameterType="String" resultType="User">SELECT * FROM userWHERE 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 userWHERE 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 userLEFT JOIN idCard ON idCard.userId = user.idWHERE 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 清除缓存的策略,默认LRU2、flushInterval 刷新间隔,单位是毫秒3、size 引用的数量4、readOnly 只是用来查询,可以提高效率
-->
<cacheeviction="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" /><!-- 默认缓存 --><defaultCachemaxEntriesLocalHeap="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"/>

相关文章:

简易实现 MyBatis 底层机制

MyBatis 大家好呀&#xff01;我是小笙&#xff0c;我中间有1年没有更新文章了&#xff0c;主要忙于毕业和就业相关事情&#xff0c;接下来&#xff0c;我会恢复更新&#xff01;我们一起努力吧&#xff01; 概述 MyBatis 是一个持久层的框架&#xff08;前身是 ibatis&#x…...

PhpPythonC++圆类的实现(OOP)

哎......被投诉了 &#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d; 其实也不是小编不更&#xff0c;这不是期末了吗&#xff08;zhaojiekou~~&#xff09;&#xff0c;而且最近学的信息收集和ctf感觉好像没找到啥能更的&#xff08;不过最经还是在考虑更一…...

OpenSSL升级版本

1 查看openssl版本 $ openssl version OpenSSL 1.0.2k-fips 26 Jan 2017 目前是1.0版本系列. 2 下载最新稳定版本的OpenSSL源码包 $ wget https://www.openssl.org/source/openssl-1.1.1q.tar.gz 3 编译源码安装 tar -xzvf openssl-1.1.1q.tar.gz cd openssl-1.1.1q .…...

基于sprinmgboot实习管理系统源码和论文

随着信息化时代的到来&#xff0c;管理系统都趋向于智能化、系统化&#xff0c;实习管理也不例外&#xff0c;但目前国内仍都使用人工管理&#xff0c;市场规模越来越大&#xff0c;同时信息量也越来越庞大&#xff0c;人工管理显然已无法应对时代的变化&#xff0c;而实习管理…...

图像分类任务的可视化脚本,生成类别json字典文件

1. 前言 之前的图像分类任务可视化&#xff0c;都是在train脚本里&#xff0c; 用torch中dataloader将图片和类别加载&#xff0c;然后利用matplotlib库进行可视化。 如这篇文章中&#xff1a;CNN 卷积神经网络对染色血液细胞分类(blood-cells) 在分类任务中&#xff0c;必定…...

Adding Conditional Control to Text-to-Image Diffusion Models——【代码复现】

官方实现代码地址&#xff1a;lllyasviel/ControlNet: Let us control diffusion models! (github.com) 一、前言 此项目的使用需要显存大于8G&#xff0c;训练自己的ControlNet或需要更大&#xff0c;因此请注意查看自身硬件是否符合。 在此之前请确保已经安装好python以及…...

java-Exchanger详解

1.概述 java.util.concurrent.Exchanger。这在Java中作为两个线程之间交换对象的公共点。 2.Exchanger简介 Exchanger类可用于在两个类型为T的线程之间共享对象。该类仅提供了一个重载的方法exchange(T t)。 当调用exchanger时&#xff0c;它会等待成对的另一个线程也调用它…...

‘再战千问:启程你的提升之旅‘,如何更好地提问?

例如&#xff0c;很多时候我们提出一些问题&#xff0c;然而通义千问提供的答案&#xff0c;并非完全符合我们的期望。这并非由于通义千问的智能程度不足&#xff0c;而是提问者的“提问技巧”尚未掌握得当。 难道提问还需要讲究艺术性吗&#xff1f;确实如此。今天&#xff0c…...

java SSM社区文化服务管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM社区文化服务管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的 源代码和数据库&#xff0c;系统主…...

go执行静态二进制文件和执行动态库文件

目的和需求&#xff1a;部分go的核心文件不开源&#xff0c;例如验证&#xff0c;主程序核心逻辑等等 第一个想法&#xff0c;把子程序代码打包成静态文件&#xff0c;然后主程序执行 子程序 package mainimport ("fmt""github.com/gogf/gf/v2/os/gfile"…...

通过示例解释序列化和反序列化-Java

序列化和反序列化是Java&#xff08;以及通常的编程&#xff09;中涉及将对象转换为字节流&#xff0c;以及反之的过程。当你需要传输或存储对象的状态时特别有用&#xff0c;比如将其通过网络发送或持久化到文件中。 序列化&#xff1a; 定义&#xff1a;序列化是将对象的状…...

k8s源码阅读环境配置

源码阅读环境配置 k8s代码的阅读可以让我们更加深刻的理解k8s各组件的工作原理&#xff0c;同时提升我们Go编程能力。 IDE使用Goland&#xff0c;代码阅读环境需要进行如下配置&#xff1a; 从github上下载代码&#xff1a;https://github.com/kubernetes/kubernetes在GOPATH目…...

Java JDBC整合(概述,搭建,PreparedStatement和Statement,结果集处理)

一、JDBC的概述&#xff1a; JDBC&#xff1a;是一种执行sql语句的Java APL&#xff0c;可以为多种关系类型数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。有了JDBC&#xff0c;Java人员只需要编写一次程序就可以访问不同的数据库。 JDBC APL&#xf…...

Nginx 负载均衡集群 节点健康检查

前言 正常情况下&#xff0c;nginx 做反向代理负载均衡的话&#xff0c;如果后端节点服务器宕掉的话&#xff0c;nginx 默认是不能把这台服务器踢出 upstream 负载集群的&#xff0c;所以还会有请求转发到后端的这台服务器上面&#xff0c;这样势必造成网站访问故障 注&#x…...

uniapp 多轴图,双轴图,指定哪几个数据在哪个轴上显示

这里使用的在这里导入&#xff0c; 秋云 ucharts echarts 高性能跨全端图表组件 - DCloud 插件市场 这里我封装成一个组件&#xff0c;自适应的&#xff0c;可以直接复制到自己的项目中 <template><qiun-data-charts type"mix":opts"opts":cha…...

Kotlin 协程 supervisorScope {} 运行崩溃解决

前言 简单介绍supervisorScope函数&#xff0c;它用于创建一个使用了 SupervisorJob 的 coroutineScope&#xff0c; 该作用域的特点&#xff1a;抛出的异常&#xff0c;不会 连锁取消 同级协程和父协程。 看过很多 supervisorScope {} 文档的使用&#xff0c;我照抄一摸一样…...

【Spring 篇】JdbcTemplate:轻松驾驭数据库的魔法工具

欢迎来到数据库的奇妙世界&#xff0c;在这里&#xff0c;我们将一同揭开Spring框架中JdbcTemplate的神秘面纱。JdbcTemplate是Spring提供的一个简化数据库操作的工具&#xff0c;它为我们提供了一种轻松驾驭数据库的魔法。本篇博客将详细解释JdbcTemplate的基本使用&#xff0…...

Web开发SpringBoot SpringMVC Spring的学习笔记(包含开发常用工具类)

开发框架学习笔记 一.Spring SpringMVC SpringBoot三者的联系SpringMVC工作原理 二.SpringBoot的学习2.1 注解2.1.1 SpringBoot的核心注解2.1.2 配置导入注解(简化Spring配置写XML的痛苦)Configuration和Bean(人为注册Spring 的 Bean)Import(补)ImportResource(补)AutowiredQua…...

微服务下的SpringSecurity认证端

从三板斧开始微服务下的SpringSecurity开始 一、引入组件包 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> 二、创建适配器 AuthorizationServerConfig…...

苹果电脑菜单栏应用管理软件Bartender 4 mac软件特点

Bartender mac是一款可以帮助用户更好地管理和组织菜单栏图标的 macOS 软件。它允许用户隐藏和重新排列菜单栏图标&#xff0c;从而减少混乱和杂乱。 Bartender mac软件特点 菜单栏图标隐藏&#xff1a;Bartender 允许用户隐藏菜单栏图标&#xff0c;只在需要时显示。这样可以…...

笙默考试管理系统-MyExamTest----codemirror(65)

笙默考试管理系统-MyExamTest----codemirror&#xff08;65&#xff09; 目录 一、 笙默考试管理系统-MyExamTest----codemirror 二、 笙默考试管理系统-MyExamTest----codemirror 三、 笙默考试管理系统-MyExamTest----codemirror 四、 笙默考试管理系统-MyExamTest---…...

git在本地创建dev分支并和远程的dev分支关联起来

文章目录 git在本地创建dev分支并和远程的dev分支关联起来1. 使用git命令2. 使用idea2.1 先删除上面建的本地分支dev2.2 通过idea建dev分支并和远程dev分支关联 3. 查看本地分支和远程分支的关系 git在本地创建dev分支并和远程的dev分支关联起来 1. 使用git命令 git checkout…...

【C++】深入了解构造函数之初始化列表

目录 一、再谈构造函数 1、引入 1&#xff09;构造函数体赋值 2&#xff09;不同成员变量赋值 2、初始化列表 一、再谈构造函数 1、引入 1&#xff09;构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值…...

差分--差分数组快速计算L到R值相加后的数组

目录 差分&#xff1a;思路代码&#xff1a; 原题链接 差分&#xff1a; 输入一个长度为 n 的整数序列。 接下来输入 m 个操作&#xff0c;每个操作包含三个整数 l,r,c &#xff0c;表示将序列中 [l,r] 之间的每个数加上 c 。 请你输出进行完所有操作后的序列。 输入格式 第…...

《NLP入门到精通》栏目导读(01/2)

一、说明 栏目《NLP入门到精通》本着从简到难得台阶式学习过度。将自然语言处理得知识贯穿过来。本栏目得前导栏目是《深度学习》、《pytorch实践》,因此,读者需要一定得深度学习基础,才能过度到此栏目内容。 二、博客建设理念 本博客基地,将建成人工智能领域的参考资料库;…...

three.js实现电子围栏效果(纹理贴图)

three.js实现电子围栏效果&#xff08;纹理贴图&#xff09; 实现步骤 围栏的坐标坐标转换为几何体顶点&#xff0c;uv顶点坐标加载贴图&#xff0c;移动 图例 代码 <template><div class"app"><div ref"canvesRef" class"canvas-…...

DHSP和DNS

一、服务程序 1.1DHCP定义 DHCP&#xff08;动态主机配置协议&#xff09;是一个局域网的网络协议。指的是由服务器控制一段IP地址范围&#xff0c;客户机登录服务器时就可以自动获得服务器分配的IP地址和子网掩码。默认情况下&#xff0c;DHCP作为Windows Server的一个服务组…...

Python冒号的解释

1. “没什么首次没有为第二个&#xff0c;跳了三个”。它得到的切片序列的每一个第三个项目。 扩展片是你想要的。新在Python 2.3 2. Python的序列切片地址可以写成[开始&#xff1a;结束&#xff1a;一步]和任何启动&#xff0c;停止或结束可以被丢弃。a[::3]是每第三个序列。…...

uniapp微信小程序投票系统实战 (SpringBoot2+vue3.2+element plus ) -后端鉴权拦截器实现

锋哥原创的uniapp微信小程序投票系统实战&#xff1a; uniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )_哔哩哔哩_bilibiliuniapp微信小程序投票系统实战课程 (SpringBoot2vue3.2element plus ) ( 火爆连载更新中... )共计21条视频…...

固乔快递查询助手:批量、快速、全面的快递信息查询软件

在快递行业飞速发展的今天&#xff0c;如何高效、准确地掌握快递信息成为了很多人的需求。而固乔快递查询助手正是解决这一难题的利器。 固乔快递查询助手是一款专注于快递信息查询的软件&#xff0c;支持多家主流快递公司查询。用户只需输入单号&#xff0c;即可快速查询到实时…...

政府网站建设运行情况/百度有刷排名软件

1 需求 统计最受欢迎的课程TopN访问次数按地市统计最受欢迎的TopN课程按流量统计最受欢迎的TopN课程 2 在MySQL中创建数据库、创建表 // 创建数据库 create database sparkSql_project;// 创建表 create table day_vedio_access_topn_stat(day varchar(8) not null,class_id…...

制作网站公司 英语网站首页/厦门关键词优化报价

前诉&#xff1a;本文章的大致内容如下 二叉排序树 1、二叉排序树查的定义 二叉排序树有称为二叉搜索树&#xff0c;二叉查找树 二叉排序树的定义&#xff1a; 二叉排序树的例子 二叉排序树性质&#xff1a;中序遍历非空的二叉排序树所得到的数据元素序列是一个按关键字…...

任何用c语言做网站/网络培训心得体会

今天在做一个功能的时候&#xff0c;需要把 Request.ServerVariables 属性绑定给 Repeater 控件显示&#xff0c;Request.ServerVariables 返回的是一个 NameValueCollection 对象&#xff0c;一个键值对的集合。 谷歌了一下&#xff0c;居然无一例外需要在 Repaeter_ItemDataB…...

独立网站需要多少钱/杭州搜索引擎排名

一、首先,明确以下内容: 1.http连接池不是万能的,过多的长连接会占用服务器资源,导致其他服务受阻 2.http连接池只适用于请求是经常访问同一主机(或同一个接口)的情况下 3.并发数不高的情况下资源利用率低下 那么,当你的业务符合上面3点,那么你可以考虑使用http连接池来提高服…...

企业门户网站框架设计/网页设计自学要多久

版权申明&#xff1a;本文为博主窗户(Colin Cai)原创&#xff0c;欢迎转帖。如要转贴&#xff0c;必须注明原文网址http://www.cnblogs.com/Colin-Cai/p/8013009.html作者&#xff1a;窗户QQ&#xff1a;6679072E-mail&#xff1a;6679072qq.com使用RSA公钥解密&#xff0c;用o…...

深圳网站建设 site/盘古百晋广告营销是干嘛

一般编程语言中喜欢用符号来判断java中两个字符串是否相等&#xff0c;例如c。c提供了操作符的重载&#xff0c;所以可以重载运算符来判断。 但是由于java中&#xff0c;没有提供运算符重载&#xff0c;而且java中没有提供基本的string类型、也没有把string看成char数组&#x…...