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

项目收获总结--MyBatis的知识收获

MyBatis的知识收获

        • 一、概述
        • 二、获取自动生成的(主)键值
        • 三、将sql执行结果封装为目标返回对象的方式和原理
        • 四、延迟加载实现原理
        • 五、批量插入
        • 六、自带分页与分页插件原理
        • 七、Mapper(Dao)接口与XML映射文件关系
        • 八、模糊查询like语句
        • 九、#{}和${}的区别
        • 十、二级缓存
          • 案例实战

一、概述

最近几天公司项目开发上线完成,做个收获总结吧~ 今天记录MyBatis的收获和提升。
在这里插入图片描述

二、获取自动生成的(主)键值

insert 方法总是返回一个 int 值 ,这个值代表的是插入的行数。若表的主键id采用自增长策略,自动生成的键值在 insert 方法执行完后可以被设置到传入的参数对象中。


<insert id=”insertUser” usegeneratedkeys=”true” keyproperty= id”>insert into table_name (name, age) values (#{name}, #{age})
</insert>
User user = new User();
user.setName(“fred”);
user.setAge(18);
int rows = mapper.insertUser(user);
// 完成后,id 已经被设置到user对象中
system.out.println(“插入数据条数:” + rows);
system.out.println(“插入数据的主键为:” + user.getid());
三、将sql执行结果封装为目标返回对象的方式和原理

方式:

第一种是使用 <resultMap> 标签,逐一定义数据库列名和对象属性名之间的映射关系。
第二种是使用 sql 列的别名功能(as 关键字),将列的别名书写为对象属性名。

原理:

使用列名与属性名的映射关系,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
四、延迟加载实现原理

Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。

原理是使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来, 然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。

不光是 Mybatis,几乎所有的ORM框架,包括 Hibernate,支持延迟加载的原理都是一样的。

五、批量插入

第一种:采用SqlSession批量插入模式

List <User> users = new ArrayList();
// 注意这里 executortype.batch
SqlSession sqlsession = SqlSessionFactory.openSession(executortype.Batch);
try {UserMapper mapper = Sqlsession.getMapper(UserMapper.Class);
for (User user: users) {mapper.insertUser(user);
}
sqlsession.commit();
} catch (Exception e) {e.printStackTrace();sqlSession.rollback();throw e;
}
finally {sqlsession.close();
}

sql:

<insert id=”insertUser”>insert into userTable (name, age) values (#{name}, #{age})
</insert>

第二种:标签在XML映射文件中构建批量插入的SQL语句

<insert id="insertBatch">INSERT INTO userTable (column1, column2, ...)VALUES<foreach collection="list" item="item" index="index" separator=",">(#{item.field1}, #{item.field2}, ...)</foreach>
</insert>

需要传入一个实体类的List集合,column1, column2, … 是表中的列名,field1, field2, … 是你的实体类中的属性名。若数据集特别大,可能需要考虑数据库事务的隔离级别、批次大小、以及可能出现的内存溢出等问题。

六、自带分页与分页插件原理

Mybatis 本身使用 RowBounds 对象进行分页,针对 ResultSet 结果集执行的内存分页,而非物理分页。
但是在sql 直接用limit等关键字可以物理分页,也可使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。

七、Mapper(Dao)接口与XML映射文件关系

Mapper(Dao)接口的全限名,就是xml映射文件中的 namespace 的值,接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给SQL的参数。

Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个 <select>、<insert>、<update>、<delete>标签,都会被解析为一个 MapperStatement 对象。

Mapper接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

八、模糊查询like语句

Mapper接口中定义方法:

List<Item> findItemsByName(@Param("name") String name);

Mapper XML文件中编写SQL查询:

<select id="findItemsByName" resultType="Item">SELECT * FROM item WHERE name LIKE CONCAT('%', #{name}, '%')
</select>

CONCAT(‘%’, #{name}, ‘%’)用于拼接SQL语句中的模糊查询字符串。
而SQL语句中拼接通配符,会有SQL注入风险:

<select id="findItemsByName" resultType="Item">SELECT * FROM item WHERE name LIKE"%"#{name}"%"
</select>
九、#{}和${}的区别

#{} 是预编译处理,${}是字符串替换。
Mybatis 在处理#{}时,会将 sql 中的#{}替换为’?'号,调用PreparedStatement的set方法来赋值;
Mybatis 在处理${}时,就是把${}替换成变量的值。
使用#{}可以有效的防止 SQL 注入,提高系统安全性。

十、二级缓存

二级缓存的和一级缓存功能一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。
一级缓存是基于sqlSession的,二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper执行sql查询到的数据,也存储在相同的二级缓存区域中。
在这里插入图片描述
开启二级缓存
一级缓存默认开启,但二级缓存需要手动开启。
首先在全局配置文件sqlMapConfig.xml文件中加入配置:

<!--开启二级缓存-->
<settings><setting name="cacheEnabled" value="true"/>
</settings>

然后在UserMapper.xml文件中开启缓存(若是基于注解形式进行查询,可以在mapper查询接口上添加@CacheNamespace注解开启二级缓存)。
要进行二级缓存的Pojo类必须实现Serializable接口
方式一:在xml文件中配置:

<mapper namespace="com.demo.mapper.UserMapper"><!-- 定义二级缓存 --><cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/><select>select * from table</select><delete>delete from table where id = #{id}</delete>...........
</mapper>

清除策略有:

LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。

方式二:@CacheNamespace

@Mapper
@CacheNamespace(eviction = FifoCache.class, flushInterval = 10000, size = 500, readWrite = true)
public interface UserMapper {// 定义 SQL 映射语句
}
案例实战

MybatisPlus整合Redis实现分布式二级缓存
MyBatis 内置的实现 PerpetualCache在分布式环境下存在问题,无法使用,因此使用implementation属性用于指定自定义缓存实现类,接下来整合Redis来实现分布式的二级缓存。
1.pom文件:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.4.1</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.24.3</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version>
</dependency>

配置文件:

mybatis-plus:mapper-locations: classpath:mybatis/mapper/*.xmlconfiguration:cache-enabled: true

Mapper接口上开启二级缓存:

@CacheNamespace(implementation = MybatisRedisCache.class,eviction = MybatisRedisCache.class)
@Mapper
public interface UserMapper extends BaseMapper<User> {}

或者使用xml映射,UserMapper.xml加入:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lagou.mapper.IUserMapper"><!--二级缓存类地址--><cache type="org.mybatis.caches.redis.RedisCache" /><select id="findAll" resultType="com.lagou.pojo.User" useCache="true">select * from user</select> 
</mapper>

配置RedisTemplate:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfiguration {private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();private static final GenericJackson2JsonRedisSerializer JACKSON__SERIALIZER = new GenericJackson2JsonRedisSerializer();@Bean@Primarypublic CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {//设置缓存过期时间RedisCacheConfiguration redisCacheCfg = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(JACKSON__SERIALIZER));return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(redisCacheCfg).build();}@Bean@Primary@ConditionalOnMissingBean(name = "redisTemplate")public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {// 配置redisTemplateRedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);// key序列化redisTemplate.setKeySerializer(STRING_SERIALIZER);// value序列化redisTemplate.setValueSerializer(JACKSON__SERIALIZER);// Hash key序列化redisTemplate.setHashKeySerializer(STRING_SERIALIZER);// Hash value序列化redisTemplate.setHashValueSerializer(JACKSON__SERIALIZER);// 设置支持事务redisTemplate.setEnableTransactionSupport(true);redisTemplate.afterPropertiesSet();return redisTemplate;}@Beanpublic RedisSerializer<Object> redisSerializer() {//创建JSON序列化器ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);//必须设置,否则无法将JSON转化为对象,会转化成Map类型objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);return new GenericJackson2JsonRedisSerializer(objectMapper);}
}

自定义缓存类:

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
@Slf4j
public class MybatisRedisCache implements Cache {// redisson 读写锁private final RReadWriteLock redissonReadWriteLock;// redisTemplateprivate final RedisTemplate redisTemplate;// 缓存Idprivate final String id;//过期时间 10分钟private final long expirationTime = 1000*60*10;public MybatisRedisCache(String id) {this.id = id;//获取redisTemplatethis.redisTemplate = SpringUtil.getBean(RedisTemplate.class);//创建读写锁this.redissonReadWriteLock = SpringUtil.getBean(RedissonClient.class).getReadWriteLock("mybatis-cache-lock:"+this.id);}@Overridepublic void putObject(Object key, Object value) {//使用redis的Hash类型进行存储redisTemplate.opsForValue().set(getCacheKey(key),value,expirationTime, TimeUnit.MILLISECONDS);}@Overridepublic Object getObject(Object key) {try {//根据key从redis中获取数据Object cacheData = redisTemplate.opsForValue().get(getCacheKey(key));log.debug("[Mybatis 二级缓存]查询缓存,cacheKey={},data={}",getCacheKey(key), JSONUtil.toJsonStr(cacheData));return cacheData;} catch (Exception e) {log.error("缓存出错",e);}return null;}@Overridepublic Object removeObject(Object key) {if (key != null) {log.debug("[Mybatis 二级缓存]删除缓存,cacheKey={}",getCacheKey(key));redisTemplate.delete(key.toString());}return null;}@Overridepublic void clear() {log.debug("[Mybatis 二级缓存]清空缓存,id={}",getCachePrefix());Set keys = redisTemplate.keys(getCachePrefix()+":*");redisTemplate.delete(keys);}@Overridepublic int getSize() {Long size = (Long) redisTemplate.execute((RedisCallback<Long>) RedisServerCommands::dbSize);return size.intValue();}@Overridepublic ReadWriteLock getReadWriteLock() {return this.redissonReadWriteLock;}@Overridepublic String getId() {return this.id;}public String getCachePrefix(){return "mybatis-cache:%s".formatted(this.id);}private String getCacheKey(Object key){return getCachePrefix()+":"+key;}
}

最后调用接口执行SQL即可测试缓存效果。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • linux在ssh的时候询问,yes or no 如何关闭
  • 自动驾驶车道线检测系列—3D-LaneNet: End-to-End 3D Multiple Lane Detection
  • Python爬虫进阶----2(细心,耐心才能爬好)
  • PostgreSQL的Json数据类型如何使用
  • Flask校验
  • Ansible服务实现自动化运维
  • 微信小程序开发入门指南
  • 在STM32嵌入式中C/C++语言对栈空间的使用
  • Matlab Git管理
  • shell 条件语句
  • 不同类型的指针变量进行++操作的效果
  • allure_pytest:AttributeError: ‘str‘ object has no attribute ‘iter_parents‘
  • MongoDB文档整理
  • JavaScript:节流与防抖
  • 主页目录导航
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • [LeetCode] Wiggle Sort
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • EOS是什么
  • ES6系列(二)变量的解构赋值
  • Facebook AccountKit 接入的坑点
  • Fundebug计费标准解释:事件数是如何定义的?
  • hadoop集群管理系统搭建规划说明
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Java小白进阶笔记(3)-初级面向对象
  • rc-form之最单纯情况
  • 从重复到重用
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 浅谈Golang中select的用法
  • 通过npm或yarn自动生成vue组件
  • 一个SAP顾问在美国的这些年
  • FaaS 的简单实践
  • Mac 上flink的安装与启动
  • ​configparser --- 配置文件解析器​
  • #AngularJS#$sce.trustAsResourceUrl
  • #etcd#安装时出错
  • #pragma once与条件编译
  • #传输# #传输数据判断#
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • #知识分享#笔记#学习方法
  • (14)学习笔记:动手深度学习(Pytorch神经网络基础)
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (八)Spring源码解析:Spring MVC
  • (第三期)书生大模型实战营——InternVL(冷笑话大师)部署微调实践
  • (附源码)基于ssm的模具配件账单管理系统 毕业设计 081848
  • (十八)三元表达式和列表解析
  • (四)Android布局类型(线性布局LinearLayout)
  • (四)鸿鹄云架构一服务注册中心
  • (一)u-boot-nand.bin的下载
  • (一)认识微服务
  • (转) ns2/nam与nam实现相关的文件
  • (转)fock函数详解
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法