mybatis持久层框架

xmlJDBC问题分析

JDBC与数据库连接过程(贾琏预执释)中的问题分析:

1)创建连接的时候,需要配置数据库驱动、账号、密码等信息,这样导致代码存在硬编码问题,同时没执行一次sql语句都要频繁的创建和释放连接。

2)准备和执行sql语句时,在创建sql语句、设置参数、获得返回结果集的时候也存在硬编码问题。

3)对结果集解析存在硬编码(查询列名的时候),封装结果集的时候需要手动封装(调用对象属性的set方法),较为繁琐。

针对以上问题的解决办法:

序号问题解决办法
1数据库配置信息硬编码问题增加配置文件
2频繁创建和释放连接连接池技术
3sql语句、参数、返回结果集硬编码问题增加配置文件
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 |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值