xmlJDBC问题分析
JDBC与数据库连接过程(贾琏预执释)中的问题分析:
1)创建连接的时候,需要配置数据库驱动、账号、密码等信息,这样导致代码存在硬编码问题,同时没执行一次sql语句都要频繁的创建和释放连接。
2)准备和执行sql语句时,在创建sql语句、设置参数、获得返回结果集的时候也存在硬编码问题。
3)对结果集解析存在硬编码(查询列名的时候),封装结果集的时候需要手动封装(调用对象属性的set方法),较为繁琐。
针对以上问题的解决办法:
序号 | 问题 | 解决办法 |
---|---|---|
1 | 数据库配置信息硬编码问题 | 增加配置文件 |
2 | 频繁创建和释放连接 | 连接池技术 |
3 | sql语句、参数、返回结果集硬编码问题 | 增加配置文件 |
4 | 手动封装结果集 | 反射和内省技术 |
自定义持久层框架
本质:对JDBC封装,规避JDBC存在的问题。
思路:
使用端:
使用配置文件提供两部分配置信息:1、数据库配置信息 (sqlMapConfig.xml)2、sql配置信息(sql语句、参数类型、返回值类型)(mapper.xml)。
框架端:本质对JDBC封装。
1)加载配置文件,根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。
创建一个资源类Resource类,类中有一个方法将配置文件加载为字节输入流:
InputStream inputStream=getResourceAsInputStream()。
为防止加载两次资源,可以将mapper.xml的全路径名配置在sqlMapConfig.xml 中。
2)将内存中字节输入流解析成两个JavaBean(容器对象:用于存放对配置文件解析出来的内容)。
第一个JavaBean命名:Configuration(核心配置类),用于存放sqlMapConfig.xml解析出来的内容。
第二个JavaBean命名:MappedStatement(映射配置类),用于存放mapper.xml解析出来的内容。
3)解析配置文件:使用dom4j技术
创建类:SqlSessionFactoryBuilder 方法:build(Inputstream in)
第一:使用dom4j技术将配置信息解析到容器对象中。
第二:创建SqlSessionFactory对象,用于生产SqlSession会话对象(涉及到使用工厂模式)
4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory。
第一:方法openSession()生产SqlSession会话对象。
5)创建SqlSession接口及实现类DefaultSqlSession,在此定义对数据库的CRUD操作:selectList(),selectOne()、update()、delete()等。
6)创建Executor接口及SimpleExecutor实现类
方法query(Configuration,MappedStatement,Object…params):执行的就是JDBC代码
mybatis持久层框架分析
Mybatis是一款轻量级的半自动持久层框架,对于开发人员来说可以优化sql,sql和java代码分开,功能边界清晰。
核心配置文件:SqlMapConfig.xml
映射配置文件:mapper.xml
SQLMapConfig.xml核心配置文件的层级关系
- configuration 配置
- properties 属性
- settings 设置
- typeAliases 类型别名
- typeHandlers 类型处理器
- ObjectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- DataSource 数据源
- databaseIDProvider 数据库厂商标识
- mappers 映射器
environment标签
<environments default="develpment"> //指定默认的环境名称
<environment id="develpment"> //当前环境
<transactionManager type="JDBC"/> //指定事务类型为JDBC
<dataSource type="POOLED"> //采用连接池技术
<property name="DriverClass" value="com.mysql.jdbc.Diver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="admin"></property>
</dataSource>
</environment>
</environments>
1) 事务管理器类型
JDBC:就是直接使用JDBC的事务提交和回滚操作。
MANAGED: 表示从不提交事务回滚操作。
2)数据源DataSource的类型:
POOLED:采用连接池技术将JDBC对象组织起来。
UNPOOLED:表示每次都去创建和关闭连接。
Mybatis注解开发
@ Insert 新增
@Delete 删除
@Update 修改
@Select 查询
@Result 对结果集进行封装
@Results可以与@Result一起使用,用于封装多个结果集
@One 实现一对一结果集封装
@Many 实现一对多结果集封装
注解 | 说明 |
---|---|
@Results | 代替标签 使用格式@Results({@Result(),@Result()} ) 或者@Results(@Result()) |
@Result | 代替标签和标签 @Result属性介绍 column:数据库的列名 property:需要封装的属性名 one:需要使用@One注解(@Result(one=@One())) many:需要使用@Many注解(@Result(many=@many())) |
一对一
public interface OrderMapper {
@Select("select * from orders")
@Results({
@Result(id=true,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.UserMapper.findById"))
})
List<Order> findAll();
}
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(int id);
}
一对多
public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "orderList",column = "id",
javaType = List.class,
many = @Many(select =
"com.lagou.mapper.OrderMapper.findByUid"))
})
List<User> findAllUserAndOrder();
}
public interface OrderMapper {
@Select("select * from orders where uid=#{uid}")
List<Order> findByUid(int uid);
}
多对多
public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "roleList",column = "id",
javaType = List.class,
many = @Many(select =
"com.lagou.mapper.RoleMapper.findByUid"))
})
List<User> findAllUserAndRole();}
public interface RoleMapper {
@Select("select * from role r,user_role ur where r.id=ur.role_id and
ur.user_id=#{uid}")
List<Role> findByUid(int uid);
}
Mybatis缓存
Mybatis缓存分为一级缓存和二级缓存,一级缓存是SqlSession级别的缓存,在操作数据库的时候需要构造SqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的SqlSession之间的缓存数据区域是相互不影响的。二级缓存是mapper级别的,多个SqlSession去操作同一个mapper的Sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨Sqlsession的。
一级缓存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d1uZ3uWZ-1588602821363)(C:\文档\学习总结\总结\图片\mybatis一级缓存.png)]
1、用户第一次查询用户ID为1的用户信息时,先去缓存中查询是否有id为1的用户,如果没有,则从数据库中查询,得到用户信息后将用户信息保存在一级缓存中,key值为statementid+params+bondSql+rowBounds。
2、如果期间SqlSession去执行了增删改的操作,并提交了事务(commit)或手动清空了缓存(ClearCache()),都会刷新一级缓存。这样做的目的是为了让缓存中的数据是最新的,避免脏读。
3、第二次发起查询用户ID为1de 用户时,先去缓存中查询,缓存中有直接从缓存中获取。
MYbatis二级缓存
一级缓存默认是开启的,二级缓存需要手动开启。首先在sqlMapConfig中配置以下代码:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
其次在**Mapper.xm文件中配置(注解方式开发时,在mapper接口类上打上注解@CacheNamespace):
<cache></cache>
执行过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xXKEuwRf-1588602821365)(C:\文档\学习总结\总结\图片\Mybatis二级缓存执行过程.png)]
Mybatis还可以针对某一条查询语句做二级缓存配置,userCache用来设置是否启用二级缓存,在statement中设置userCache=false 表示不启用二级缓存配置,每次都去数据库查询。该值默认为true;flushCache刷新缓存。当采用注解开发的时候,需要在mapper接口层方法上加上注解@options(userCache=false)
<select id="selectUserByUserId" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
select * from user where id=#{id}
</select>
<select id="selectUserByUserId" flushCache="true" useCache="false"
resultType="com.lagou.pojo.User" parameterType="int">
select * from user where id=#{id}
</select>
一般情况下执行完commit操作后需要刷新二级缓存,避免脏数据。flushCache默认值为true,所以不需要配置。
Mybatis整合Redis实现缓存(分布式)
在分布式环境下,Mybatis自带的二级缓存无法满足需求;Mybatis二级缓存是通过实现Cache接口实现的。所以当我们自定义二级缓存的时候需要实现Mybatis包下的Cache接口,此外在配置文件中配置Cache的实现类(朱注解开发模式下@CacheNamespace(implementation=XXXCacheImp.class))。针对Redis当前主流NoSql数据库,Mybatis对Redis实现了整合。在使用redis做为缓存时需要导入Mybatis-Redis的包。
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
在Mapper.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">
<!--配置采用哪个缓存,RedisCache实现了Mybatis包下的Cache接口-->
<cache type="org.mybatis.caches.redis.RedisCache" />
<select id="findAll" resultType="com.lagou.pojo.User" useCache="true">
select * from user
</select>
如果想配置不是默认情况下的Redis信息,则在Resource目录下配置Redis信息
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
Mybatis插件机制
Mybatis插件原理底层就是用JDK动态代理实现的。
Mybatis所允许的拦截方法如下:
- 执行器Executor(update、query、commit、rollback等方法);
- Sql语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
- 参数处理器ParameterHandler( getParameterObject̵、setParameters等方法 );
- 结果处理器ResultSetHandler( ҁhandleResultSets̵ 、 handleOutputParameters 等方法);
自定义插件
Mybatis插件实现interceptor接口:
* intercept方法:插件得核心方法,对方法进行增强
* plugin方法:生产target的代理对象;
* setProperty方法:传递插件所需参数。
@Intercepts({//大括号表示一个数组,表示可以配置多个Signature注解,对多个方法进行拦截
@Signature(type = StatementHandler.class,//拦截的目标类
method = "prepare",//拦截的方法
args = { Connection.class, Integer.class}),//拦截方法的入参,按照顺序写在这儿
})
public class MyPlugin implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
//拦截器的拦截方法
@Override
public Object intercept(Invocation invocation) throws Throwable {
//增强逻辑处理
System.out.println("对方法进行了增强....");
return invocation.proceed(); //执行原方法
}
/**
* // 主要作用是把拦截器生成一个代理放在拦截器链中
* @Description 包装目标对象,为目标对象生成代理对象
* @Param target要拦截的目标对象
* @Return 代理对象
*/
@Override
public Object plugin(Object target) {
System.out.println("将要包装的目标对象"+target);
return Plugin.wrap(target,this);
}
/** ឴获取配置文件的属性**/
// 插件初始化的时候调用,也只调用一次,
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化参数"+properties);
}
}
SqlMapConfig.xml
<plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!--配置参数-->
<property name="name" value="Bob"/>
</plugin>
</plugins>
PageHeper插件
通用mapper插件
通用mapper就是解决单标的增删改操查操作。开发人员不需要重复写单表的增删改查的操作。
1、pom文件
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>3.1.2</version>
</dependency>
2、Mybatis配置文件中配置
<plugins>
<plugin
interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
<property name="mappers"
value="tk.mybatis.mapper.common.Mapper"/>
</plugin>
</plugins>
3、实体类设置
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
}
Mybatis源码分析
传统方式源码执行过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-THeRRNFb-1588602821367)(C:\文档\学习总结\总结\图片\mybatis源码执行过程.png)]
mapper接口(动态代理方式)分析
mybatis用到的设计模式
模式 | mybatis体现 |
---|---|
Build构建者模式 | 例如:SQLSessionFactoryBuilder、Environment; |
工厂模式 | SQLSessionFactory、TransactionFactory、LogFactory |
单例模式 | LogFactory和ErrorContext |
代理模式 | mybatis的核心实现,比如mapperProx |
组合模式 | 例如SqlNode和各个子类chooseSqlNode |
模板方法模式 | 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和其所有子类 |
适配器模式 | 例如Log的Mybatis接口和他对JDBC、log4j等各种日志框架的适配实现 |
装饰者模式 | 例如Cache包中的Cache.decorators子包中各个装饰者的实现 |
迭代器模式 | PropertyTokenizer |
|
| 代理模式 | mybatis的核心实现,比如mapperProx |
| 组合模式 | 例如SqlNode和各个子类chooseSqlNode |
| 模板方法模式 | 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和其所有子类 |
| 适配器模式 | 例如Log的Mybatis接口和他对JDBC、log4j等各种日志框架的适配实现 |
| 装饰者模式 | 例如Cache包中的Cache.decorators子包中各个装饰者的实现 |
| 迭代器模式 | PropertyTokenizer |