2020年8月25日20:55:29
自定义持久层框架
- 持久层框架 是对jdbc 的封装, 并解决了jdbc存在的问题
jdbc 代码基础回顾
try{
Class.formName("com.mysql.jdbc.Driver");
connection = .....
}cache(Exception e){
}
- 传统jdbc 存在 数据库配置信息存在硬编码问题 当更换数据库后需要再次对数据库驱动信息进行更改
- 频繁创建释放数据库连接
- sql语句 设置参数 获取结果集等参数均存在硬编码问题
- 手动封装结果集 较为繁琐
解决传统jdbc存在的问题
- 硬编码 --> 配置文件
- 硬编码 --> 连接池
- 手动封装结果集 --> 反射 内省
自定义持久层框架设计思路
使用端 --> 项目
- 引入自定义持久层jar
- 提供数据库配置信息及sql配置信息
- sql配置信息
- sql语句
- 参数类型
- 返回值类型
- sql配置信息
- 使用配置文件提供外上述两种配置信息
- sqlMapConfig.xml 存放数据库的配置信息,
存入mapper.xml全路径
- mapper.xml 存放sql配置信息
自定义持久层框架本身
-
创建为项目工程,本质是对jdbc代码进行封装
-->
底层为传统的jdbc- 数据库配置信息
- sql 配置信息 占位符 参数 sql语句等
- 根据调用处传递的配置信息进行解析
- 加载配置文件
-->
以字节输入流的形式加载-->
存储在内存中 - 创建Resource类,getResourceAsStream(String path)
- 创建2个javaBean(容器对象),存放的是对配置文件解析出的内容
- Configuration:核心配置类: 存放sqlMapperConfig.xml解析的内容
- MappedStrement:映射配置类:存放mapper.xml解析的内容
- 解析配置文件:Dom4j
- 创建类:SqlSessionFactoryBuilder,存在方法:build(InputStream in)
- build方法实现逻辑:
- 使用dom4j解析配置文件,将解析结果封装到容器对象内
- 创建sqlSessionFactory对象,生产sqlSession(会话对象),避免重复创建连接
-->
工厂模式 降低耦合
- 创建sqlSessionFactory接口及实现类DefaultSqlSessionFactory
- openSession方法 : 创建 sqlSession对象
- 创建SqlSession 接口及实现类DefaultSqlSession 定义对数据库的CRUD方法
- selectList()
- selectOne()
- update()
- delete()
- 创建Excutor接口及实现类 SimpleExcutor 实现类
- query(Configuration conf,MappedStatement mapped,Object ...params):执行JDBC代码
- 加载配置文件
graph TB b("工程项目") subgraph 工程项目 d("mapper.xml") --"mapper.xml全路径"--> c("sqlMapConfig.xml") b --"sql执行语句"--> d b --"数据库配置"--> c end b --"引用"--> a subgraph 自定义持久层 tip("本质是对jdbc代码的封装") a("自定义持久层") tip --> a a --"1. 创建"--> a1("Resources类") a1 --> a1a("InputStream()") a1 --"加载配置文件为字节输入流"--> a1b("getResourceAsStream(String path)") a --"2. 创建"--> a2("容器javaBean") subgraph 容器 a2 --"创建核心配置类"--> a2a("Configuration") a2a --"存放"--> a21a("sqlMapConfig.xml 解析的内容") a2 --"创建核心配置类"--> a2b("MappedStatement") a2b --"映射配置类"--> a2b1("mapper.xml 解析的内容") end a --"3. 创建"--> a3("SqlSessionFactory") a3 --"实现类"--> a3a("DefaultSqlSessionFactory") a3a --"方法"--> a3a1("openSession()") a3a1 --"生产"--> a3a1a("Sqlsession") a --"4. 解析配置文件dom4j"--> a4("SqlSessionFactoryBuilder") a4 --"方法"--> a41("build(inputStream in)") a41 --"使用dom4j解析配置文件" --> a4a1a("解析结果") a4a1a --"封装到容器对象"--> a2 a41 --"创建"--> a41b("SqlSessionFactory对象") a41b --"工厂模式生产"--> a41b1("sqlSession会话对象") a --"5. 创建"--> a5("SqlSession接口") a5 --"实现类"--> a5a("DefaultSession") a5a --"列表查询"--> a5a1("selectList()") a5a --"查询单个"--> a5a2("selectONe()") a5a --"修改"--> a5a3("update()") a5a --"删除"--> a5a4("delete()") a --"6. 创建"--> a6("Excutor接口") a6 --"实现类"--> a6a("SimpleExcutor") a6a --"成员方法"--> a6a1("query(Configuration conf,MappedStatement st,Object ...params);") a6a1 --"执行jdbc代码" -->a6a1a("sql语句") end
mybatis
简介
-
持久层框架
-
基于
orm:Object Relation Mapping
实体类和数据库表建立映射关联关系 -
半自动 可以对sql进行优化
hibernate -> 全自动
-
轻量级:启动的过程中需要的资源比较少
-
底层: 对jdbc执行代码 进行封装 规避硬编码,频繁开闭数据源
sqlMapConfig.xml
节点说明
- environments 数据库环境的配置,支持多环境配置
- environments.default
-->
指定mybatis 使用的默认环境名称 - environment.id
-->
当前环境的名称 - environment - transcationManager.type="JDBC" 指定事务管理类型为jdbc
- environment - dataSource.type="POOLED" 指定数据源类型为连接池
- environment - dataSource
- property.driver 数据库驱动
- property.url 数据库地址 注意连接编码格式
- property.user 数据库连接用户名
- property.password 数据库连接用密码
- mapper 配置的四种方式:
-
mapper.resource
相对于类路径的引用 -
mapper.url
完全限定资源定位符 -
mapper.class
接口对应的全路径 -
package.name
批量加载 保证映射文件与接口同包同名
-
传统dao层开发方式
dao层开发方式
mybatis 外部properties 文件
- 设置mybatis xml配置文件的约束头
- 在resources下创建外部配置文件 格式:
对象.属性=值
- 加载外部配置文件
- 在 configutation 后第一个节点之前 引入外部配置文件
<properties resource='路径'/>
- 在 configutation 后第一个节点之前 引入外部配置文件
mybatis aliasType
- 在mybatis 配置文件中的typeAliases 节点中设置
<typeAliases> <typeAlias type="pojo类路径的全限定名" alais="别名"></typeAlias> </typeAliases> // 对于基础数据类型 mybatis 已定义了别名 string ==> String long ==> Long int ==> Integer double ==> Double boolean ==> Boolean
- 当存在多个pojo时不适用以上方法,采用package模式[批量起别名]
- package模式
不要单独指定alias属性 默认为类本身的类名,且不区分大小写
<typeAliases> <package name="pojo类所在包的全限定名"></typeAlias> // 不要单独指定alias属性 默认为类本身的类名,且不区分大小写 </typeAliases>
- package模式
mapper.xml
动态sql
-
if
标签 判断入参是否符合条件, 不符合不拼接 -
where
标签 自动拼接where 同时去掉where后的第一个and 关键字 -
foreach
标签-
collection
array 注意便携式不要使用#{} -
open
循环前追加 -
close
循环结束后追加 -
item
单次循环内的数据对象 生成的变量 -
separator
前后循环数据之间的分隔符
-
-
sql
标签 抽取sql片段- id sql语句的标识
- 内容为具体的sql语句 可用于封装 分页 和 查询某张表时的前面部分
mybatis复杂映射开发
sqlMapConfig.xml 内引入 mapper.xml
<package name="与接口同包同名的包名"><package>
一对一查询
-
resultMap
手动配置实体属性与表字段的映射关系 可用于mybatis 的resultMMap- id
- type 按照封装对象的全路径
<resultMap id="orderMap" type="com.lagou.pojo.Order"> <result property="id" column="id"></result> <result property="orderTime" column="orderTime"></result> <result property="total" column="total"></result> <association property="user" javaType="com.lagou.pojo.User"> <result property="id" column="uid"></result> <result property="username" column="username"></result> </association> </resultMap> <!--resultMap:手动来配置实体属性与表字段的映射关系--> <select id="findOrderAndUser" resultMap="orderMap"> select * from orders o,user u where o.uid = u.id </select>
一对多查询
<resultMap id="userMap" type="com.lagou.pojo.User">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
<collection property="orderList" ofType="com.lagou.pojo.Order">
<result property="id" column="id"></result>
<result property="orderTime" column="orderTime"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user u left join orders o on u.id = o.uid
</select>
多对多查询
- 查询用户的同时查询出用户的角色
<resultMap id="userRoleMap" type="com.lagou.pojo.User">
<result property="id" column="userid"></result>
<result property="username" column="username"></result>
<collection property="roleList" ofType="com.lagou.pojo.Role">
<result property="id" column="roleid"></result>
<result property="roleName" column="roleName"></result>
<result property="roleDesc" column="roleDesc"></result>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
select * from user u left join sys_user_role ur on u.id = ur.userid
left join sys_role r on r.id = ur.roleid
</select>
mybatis 注解开发
使用注解开发 无需编写任何配置文件
- @Insert 增
- @Update 删
- @Delete 改
- @Select 查
- @Result 实现对结果集的封装 替代result标签节点
- @Results 替代resultMap 标签节点
- @One 替代单个pojo对象 替代 association 标签节点
- @Many 如果属性为集合时 则采用 此注解
注解一对多
@Results({
@Result(property = "id",column = "id"),
@Result(property = "orderTime",column = "orderTime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",javaType = User.class,
one=@One(select = "com.lagou.mapper.IUserMapper.findUserById"))
})
@Select("select * from orders")
public List<Order> findOrderAndUser();
注解多对多
# IUserMapper
//查询所有用户、同时查询每个用户关联的角色信息
@Select("select * from user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "roleList",column = "id",javaType = List.class,
many = @Many(select = "com.lagou.mapper.IRoleMapper.findRoleByUid"))
})
public List<User> findAllUserAndRole();
# IRoleMapper
@Select("select * from sys_role r,sys_user_role ur where r.id = ur.roleid and ur.userid = #{uid}")
public List<Role> findRoleByUid(Integer uid);
mybatis 缓存
一级缓存
使用同一个sqlSession 对象时,数据会被缓存
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从
数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。 - 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的
一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 - 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直
接从缓存中获取用户信息
一级缓存分析
- 一级缓存到底是什么
底层是一个hashMap 参照上述流程图
- 一级缓存什么时候被创建
缓存key创建类: Excustor.class.createCacheKey() BaseExcutor.createCacheKey() //创建一级缓存的key CacheKey.update() - updateList.add(configuration.getEnviroment().getId());
- 一级缓存的工作流程是怎样的
- 一级缓存的清空
sqlSession.close()
或者执行带有事务的操作 - 在分布式环境下也会存在
脏读
问题解决:禁用一级缓存或调整缓存为statement 级别
二级缓存
- 原理 类似 一级缓存
- 操作的sqlSession 执行了事务操作后 会清空二级缓存
- 一级缓存 默认开启
- 二级缓存需要手动配置开启
1. xml开发形式 <setting> <setting name="cacheEnabled" value="true" /> </setting> 2. 注解形式 在dao接口上添加注解 @CacheNamespace
- 二级缓存 缓存的不是对象 而是对象中的数据
- pojo类需要实现 Serializable 接口
- 二级缓存的存储机制是多样的,可能存储在硬盘上 或者内存中
- 当执行带有事务的操作后,二级缓存会被清空
- 当基于xml配置时还可以配置
- useCache 属性 false 禁用二级缓存 每次查询都发送sql去数据库查询 默认为true
注解形式:@select 注解之前添加 @Options(cache="false")
- flushCache 属性: 每次增删改操作后 清空缓存 默认true
二级缓存整合redis
-
PerpetualCache
是mybatis默认实现缓存功能的类 - 二级缓存的底层数据结构 还是 HashMap
org.apache.ibatis.cache.impl.PerpetualCache --> private Map<Object, Object> cache = new HashMap()
- 指定mybatis二级缓存的实现类
在dao接口上添加注解 @CacheNamespace(implementation=PerpetualCache.class) PerpetualCache.class 更换为自定义缓存实现类 如 : @CacheNamespace(implementation = RedisCache.class)
- 二级缓存 存在的问题
- 单服务器下 --> 没问题
- 分布式 --> 无法实现分布式缓存
- 分布式缓存技术
- redis 官方提供有mybatis-redis实现类
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> 配置配置文件 : redis.properties 配置以下属性: host=localhost port=6379 connectionTimeout=5000 password=123456 database=0
- memcached
- ehcache
- 二级缓存存在的脏读问题分析
redis-cache 分析
- redis-cache 是如何存取值的
1. 必须实现mybatis 的cache 接口 2. 使用 jedis.hget 进行取值
- redis-cache 使用的是哪种redis 数据结构
redis 的 hash 数据类型
mybatis的插件
- 一种形式的拓展点
- 增加灵活性
- 根据实际需求 自行拓展
- mybatis 的四大组件 及允许拦截的方法
- Executor 执行器
- update
- query
- commit
- rollback 等
- StatementHandler sql语法构建器
- prepare
- parameterize
- batch
- update
- query 等
- ParameterHandler 参数处理器
- getParameterObject
- setParameterObject
- ResultSetHandler 结果集处理器
- handleResultSets
- handleOutputParameters
- Executor 执行器
- 插件
拦截器
底层是对以上四个组件的拦截,采用动态代理实现
以上组件的创建原理
- 每个创建出来的对象不是直接返回的,而是
interceptorChain.pluginAll(parameterHandler)
- 获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用
interceptor.plugin(target)
;返回target包装后的对象 - 插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;
- 由于使用了jdk动态代理,那么就回去执行 invoke 方法,可以在拦截器 的前置和后置处理器内做相应的处理操作
@Intercepta({
@signature(
type=Executor.class,
method="query",
args=(MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
public class ExamplePlugin implements Interceptor{
//省略逻辑
}
自定义mybatis 插件
- 插件 --> 拦截器
org.apache.ibatis.plugin.Interceptor 类
# 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
Interceptor.intercept
# 主要为了把当前的拦截器生成代理存到拦截器链中
Interceptor.plugin
# 获取配置文件的参数
Interceptor.setProperties
- 定义
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
# 可以多个Signature
@Intercepts({
@Signature(type= StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}),
@Signature(type= Executor.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {
/*
拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法:" + invocation.getMethod().getName() + " 进行了增强...." );
return invocation.proceed(); //原方法执行
}
/*
主要为了把当前的拦截器生成代理存到拦截器链中
*/
@Override
public Object plugin(Object target) { Object wrap = Plugin.wrap(target, this); return wrap; }
/*
获取配置文件的参数
*/
@Override
public void setProperties(Properties properties) { System.out.println("获取到的配置文件的参数是:"+properties); }
}
- 配置
<plugins>
<plugin interceptor="com.lagou.plugin.MyPlugin">
<property name="name" value="tom"/> <!-- 设置参数 -->
</plugin>
</plugins>
- 使用
1. MyPlugin.plugin --> MyPlugin.intercept --> MyPlugin.setProperties
mybatis的第三方插件
- PageHelper
- 导入maven 依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency>
- 在mybatis配置文件中配置PageHelper
在 plugins 节点内 添加 plugin 并设置属性 `property` 方言 `dialect` 值设 `mysql` <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin>
- 通用 mapper
- 导入依赖
<dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.1.2</version> </dependency>
- mybatis 的配置文件内配置 通用mapper 的plugin
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> <!--指定当前通用mapper接口使用的是哪一个--> <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/> </plugin>
- 实体类设置关联
@Table(name = "user") public class User implements Serializable { @Id //对应的是注解id @GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue strategy 设置主键的生成策略 // GenerationType.IDENTITY 底层必须支持自增长 // GenerationType.SEQUENCY 底层不支持自增长 // GenerationType.TABLE 从表中取值 生成主键 // GenerationType.AUTO 自动选择合适的生成主键 private Integer id; @Column(name="123") 当与表的字段不同时,采用@Column 保持同步 private String username; // Get Set 略 }
- 创建dao接口 继承
tk.mybatis.mapper.common.Mapper
传入泛型
egg: public interface UserMapper extends Mapper<User> {}
- 使用
- 传统调用
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(1); User user1 = mapper.selectOne(user); System.out.println(user1);
-
Example
方式调用
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // example 方式调用 Example example = new Example(User.class); // 查询User表中的全部记录 example.createCriteria().andEqualTo("id",1); // 执行查询,获得结果 List<User> users = mapper.selectByExample(example); for (User user2 : users) { System.out.println(user2); }
随堂测试01 自定义持久层框架
-
自定义持久层框架IPersistence是如何解决JDBC存在的问题:() [多选题]
- [x] A. 用配置文件解决了硬编码问题
- [x] B.使用了C3P0连接池解决了频繁创建释放数据库连接问题
- [x] C.在simpleExecute中使用到了反射进行了参数的设置
- [x] D.在simpleExecute中使用了内省进行了返回结果集的封装
-
在进行自定义持久层框架IPersistence优化时,主要为了解决那些问题:() [多选题]
- [x] A.应用在Dao层,整个操作的模板代码重复
- [x] B.调用sqlSession方法时、参数statementId硬编码
- [ ] C.无法保证statementId的唯一性
- [ ] D.参数存在硬编码
-
下列关于Configuration及MappedStatement配置类,说法正确的是:() [多选题]
- [x] A.使用dom4j对sqlMapConfig.xml解析时,会将解析出来的内容以不同形式封装到Configuration对象中
- [ ] B.使用dom4j对mapper.xml解析时,每个