MyBatis工作原理?
MyBatis基本工作原理介绍
mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件中构建出SqlSessionFactory,然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。
1,读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
2,加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3,构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。
4,创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
5,Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
6,MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
7,输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。
8,输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。
Mybatis中的缓存机制?
缓存指存在内存中的临时数据,使用缓存减少和数据库交互的次数,提高效率。将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;
mybatis缓存包括:
首次查询时会先从数据库查询并将结果放入一级缓存,在关闭 SqlSession 前会将一级缓存数据写入二级缓存。再次查询时,如果一级缓存没有找到结果,则会尝试从二级缓存中获取结果,避免了再次访问数据库。
Mybatis中的缓存机制
⼀级缓存
将查询到的数据存储到SqlSession中。范围比较小,针对于一次sql会话
⼀级缓存默认是开启的。不需要做任何配置。在一次 SqlSession 中(数据库会话),程序执行多次查询,且查询条件完全相同,多次查询之间程序没有其他增删改操作,则第二次及后面的查询可以从缓存中获取数据,不会走数据库,会走缓存
什么时候一级缓存失效?
第一次查询和第二次查询之间你做了以下两件事中的任意一件,都会让一级缓存清空:
1,执行了sqlSession的clearCache()方法,这是手动清空缓存。
2,执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。
⼆级缓存
将查询到的数据存储到SqlSessionFactory中。范围比较大,针对于整个数据库级别的。
二级缓存的范围是SqlSessionFactory。
使用二级缓存需要具备以下几个条件:
1,<setting name="cacheEnabled" value="true">
这个配置表示启用 MyBatis 的二级缓存功能。默认就是true,⽆需在配置文件设置。
2,在需要使⽤⼆级缓存的StudentMapper.xml⽂件中添加配置:<cache/>
<mapper namespace="com.yjg.mapper.StudentMapper">
<!-- 默认情况下,二级缓存机制是开启的。-->
<!-- 只需要在对应的StudentMapper.xml文件中添加以下标签。用来表示”我"使用该二级缓存。-->
<cache/>
<select id="queryStudentById" parameterType="integer" resultType="Student">
select * from student where stu_id=${id}
</select>
//useCache="false"表示不走缓存
<select id="queryStudentWithoutCache" useCache="false" parameterType="integer" resultType="Student">
select * from student where stu_id=${id}
</select>
</mapper>
3,使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
public class Student implements Serializable {}
4,SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤
public void test() throws Exception {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。sqlSession1是一级缓存。
Student student1 = mapper1.queryStudentById(2);
System.out.println(student1);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
sqlSession1.close();
//会先走二级缓存,再走一级缓存
//二级缓存SqlSessionFactory级别 90%找到
//一级缓存是SqlSession级别的 5%找到
Student student2 = mapper2.queryStudentById(2);
System.out.println(student2);
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
sqlSession2.close();
}
MyBatis集成EhCache
集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
对SqlSessionFactory的理解?
SqlSessionFactory是mybatis中非常核心的api,是一个SqlSessionFactory工厂。目的是创建SqlSession对象。
SqlSessionFactory是单例的,SqlSessionFactory对象的创建通过SqlSessionFactoryBuilder来实现,在SqlSessionFactoryBuilder中完成了SqlSessionFactory对象的创建,也完成了全局配置文件和相关映射文件的加载和解析操作。
相关加载解析的信息会保存在Configuration对象中。其中涉及到工厂模式和建造者模式。
对SqlSession的理解?
SqlSession是mybatis中非常核心的api,通过相关api实现对应数据库数据的操作。
SqlSession对象通过SqlSessionFactory获取,是一个会话级别的,当新的会话到来时,我们需要创建新的SqlSession对象来处理,当会话结束时关闭相关会话资源。
处理请求的方式
1,通过相关的增删改查的api直接处理
2,可以通过getMapper(xxx.class)来获取相关mapper接口的代理对象来处理
spring中如何解决DefaultSqlSession数据安全问题?
DefaultSqlSession是线程不安全的,不能把DefaultSqlSession声明在成员变量中。
在spring中提供了SqlSessionTemplate(线程安全)来实现SqlSession相关的定义。在SqlSessionTemplate中每个方法通过SqlSessionProxy来操作。这是一个动态代理对象,然后在动态代理对象中通过方法级别的DefaultSqlSession来实现相关数据库的操作。
MyBatis延迟加载?
Mybatis延迟加载使用及原理
MyBatis的延迟加载是指当需要访问一个对象的关联对象时,并不是在查询主对象的同时就加载这些关联对象,而是在实际使用到关联对象时才去查询加载。这样做的好处是可以提升查询的性能,特别是对于关联关系较为复杂,数据量较大的情况。
使用延迟加载
在MyBatis配置文件中可以设置延迟加载的属性:
<settings>
<!-- 启用延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当使用到关联对象的某个属性时才加载该对象 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
在映射文件中,可以通过fetchType="lazy"来指定延迟加载:
ClassMapper.xml文件
<mapper namespace="com.pro.dao.ClassDao">
<resultMap id="classMap" type="classes">
<id property="classId" column="classId"/>
<result property="className" column="className"/>
</resultMap>
<select id="selectClass" parameterType="int" resultMap="classMap">
select *from classes where classId=#{classId}
</select>
</mapper>
StudentMapper.xml文件
<mapper namespace="com.pro.dao.StudentDao">
<resultMap id="stuMap" type="student">
<id property="stuId" column="stuId"/>
<result property="stuName" column="stuName"/>
<result property="age" column="age"/>
<association property="cs" javaType="Classes" column="classId"
select="com.pro.dao.ClassDao.selectClass"/>
</resultMap>
<select id="selectStu" resultMap="stuMap" parameterType="int">
select * from student where stuId=#{stuId}
</select>
</mapper>
DO实体类
@Data
public class Student {
private Integer stuId;
private String stuName;
private Integer age;
// 此属性通过MyBatis懒加载
private Classes classes;
}
service类调用
//先加载主要信息,关联信息并没有直接加载出来
Student stu = stuMapper.selectStu(1);
//当需要加载关联信息时,通过延迟加载,即可加载出来
Student stu = stuMapper.selectStu(1);
Classes classes = stu.getCs();
MyBatis 中 #{}和 ${}的区别是什么?
#{}是预编译处理,在使用 #{}时,MyBatis 会将 SQL 中的 #{}替换成“?”,
<!--修改操作-->
<update id="update" parameterType="com.benjamin.domain.User">
update user set username=#{username},password=#{password} where id=#{id}
</update>
${}是字符替换,比如读取property文件中数据库连接信息
<!--数据源环境-->
<environments default="developement">环境,当配置多个environment时,default=指定默认的环境名称
<environment id="developement">指定当前环境的名称
<transactionManager type="JDBC"></transactionManager>事务管理器,指定事务管理类型是JDBC
<dataSource type="POOLED">指定当前数据源类型是连接池
<property name="driver" value="${jdbc.driver}"/>数据源配置的基本参数
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
MyBatis 分页插件的实现原理是什么?
分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 SQL,然后重写 SQL,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
1导入通用PageHelper的坐标
2在mybatis核心配置文件中配置PageHelper插件
<!--配置分页助手插件-->
<plugins>
<!--拦截器-->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!--配置分页方言-->
<property name="dialect" value="mysql"></property>
</plugin>
</plugins>
3,调用mapper接口方法前设置分页参数
//设置分页相关参数 当前页+每页显示的条数
PageHelper.startPage(2,2);
实体类为什么需要实现序列化接口 Serializable?
MyBatis-Plus 在进行一些操作时(比如缓存、网络传输等),需要将实体类对象转换成字节流进行传输或存储,而实现序列化接口可以让对象在网络中进行传输或者在磁盘上进行存储。
因此,为了确保对象的序列化和反序列化过程能够正常进行,实体类通常需要实现 Serializable 接口。这样做可以提高对象的可传输性和持久化能力,使得实体类更加适合在分布式系统中使用。
transient关键字
Java中的transient
关键字用于指示一个成员变量不应该被序列化。当一个对象被序列化时,它的状态会被转换成字节序列,这些字节序列可以存储在磁盘上或通过网络传输。transient
修饰的成员变量不会包含在序列化的结果中,也就是说,这些变量不会被写入到序列化流中。
当需要给entity实体类中加一个非数据库字段时,可以使用transient关键字修饰字段
MyBatisPlus示例
1,entity实体类
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("APPLY_ORDER_INFO")
public class ApplyOrderDO extends BaseDO {
@TableId(value = "ID", type = IdType.ASSIGN_UUID)
private String id;
@TableField(value = "ORDER_ID")
private String orderId;
@TableField(value = "USER_ID")
@NotBlank
private String userId;
}
2,基础字段自动注入
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseDO {
@TableField(value = "CREATE_TIME", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT")
private Date createTime;
@TableField(value = "UPDATE_TIME", fill = FieldFill.UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT")
private Date updateTime;
@TableLogic
@Null(groups = {Insert.class, Update.class}, message = "逻辑删除不用传")
@TableField(value = "DELETE_FLAG", fill = FieldFill.INSERT)
private Integer deleteFlag;
@TableField(value = "DELETE_TIME", fill = FieldFill.UPDATE)
private Date deleteTime;
}
@Component
public class MybatisPlusAutoFillConfig implements MetaObjectHandler {
// 新增时填充
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("deleteFlag", 0, metaObject);
this.setFieldValByName("createTime", new Date(), metaObject);
}
// 修改时填充
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
3,mapstruct用于实体类转换为vo类
//本文件创建的位置要根据转换目标来定;比如是DO转VO,则写在DO的目录里,如果是DTO转DO,则写到DTO下
/**
* 注意:unmappedTargetPolicy
* ERROR:任何未映射的目标属性都将使构建失败–这可以避免意外的未映射字段
* WARN:(默认)构建期间的警告消息
* IGNORE:无输出或错误
*/
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface ApplyOrderMapper {
ApplyOrderMapper INSTANCE = Mappers.getMapper(ApplyOrderMapper.class);
ApplyOrderVO toVO(ApplyOrderDO applyOrderDO);
@Mapping(target = "optimizeJoinOfCountSql", ignore = true)
Page<ApplyOrderVO> toVOPage(Page<ApplyOrderDO> page);
}
4,mapper接口
@Mapper
public interface IApplyOrderMapper extends BaseMapper<ApplyOrderDO> {
}
5,service实现类
@Service
public class ApplyOrderServiceImpl implements IApplyOrderService {
private final IApplyOrderMapper applyOrderMapper;
@Autowired
public ApplyOrderServiceImpl(IApplyOrderMapper applyOrderMapper) {
this.applyOrderMapper = applyOrderMapper;
}
@Override
public Object createApplyOrder(ApplyOrderDTO applyOrderDTO) {
//mybatisplus查数据库
LambdaQueryWrapper<ApplyOrderDO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ApplyOrderDO::getOrderId, applyOrderDO.getOrderId());
long existsRows = applyOrderMapper.selectCount(queryWrapper);
return null;
}
}