栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

01、mybatis使用回顾

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

01、mybatis使用回顾

1、基本概念

MyBatis是⼀款优秀的基于ORM的半⾃动轻量级持久层框架,它⽀持定制化SQL、存储过程以及⾼级映射。MyBatis避免了⼏乎所有的JDBC代码和⼿动设置参数以及获取结果集。MyBatis可以使⽤简单的XML或注解来配置和映射原⽣类型、接⼝和Java的POJO (Plain Old Java Objects,普通⽼式Java对 象)为数据库中的记录。
官网地址 : 官网地址

2、常用配置介绍使用

为了方面后续学习使用,先搭建一个maven工程,引入mybatis 依赖,另外因为要连接数据库,还需要引入jdbcdriver,为方便测试引入junit, 具体pom 配置如下:



    4.0.0

    com.example
    mybatis_quickstart
    1.0-SNAPSHOT

    
        UTF-8
        UTF-8
        1.8
        1.8
        1.8
    


    
        
            org.mybatis
            mybatis
            3.4.5
        
        
            mysql
            mysql-connector-java
            5.1.6
        
        
            junit
            junit
            4.11
        
    


搭建项目结构如下,其中jdbc.properties 存储数据库连接配置信息(可以配置在sqlmapconfig中,但因为数据库信息每个环境都不一样,常见方式是单独放在与环境相关的文件中,为此单独放在jdbc.properties),sqlmapConfig.xml 是mybatis 核心配置文件,usermapper.xml是 mybatis sql映射配置文件存储信息
文件内容如下:
sqlmapConfig.xml中





    
    

    
    
        

        
        
    

    
    
        
        
            
            
            
            

                

                
                
                
                
                
            
        
    
	
	
    
    
	
    
    

	
    
        
    
    

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/de?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

usermapper.xml






    
    
    select * from user
 
    
    
        
    

    
        insert into user(id,username) values (#{id},#{username})
    

    
        update user set username=#{username} where id = #{id}
    

    
        delete from  user where id = #{id}
    

    
        select *  from  user
        
            
               and  id = #{id}
            
            
                and  username = #{username}
            
        
    


    
        select * from user u,orders o  where o.uid=u.id;
    

public interface IOrderMapper {

    //查询订单的同时,查询所属的用户 一对一
    public List findOrderAndUSer();
}
3.2 一对多查询结果映射

一个人对应多个订单,为此:用户表新增订单列表集合

public class User implements Serializable {


    private Integer id;

    private String username;

    private List orderList;

    private List roleList;

	// 省略 set get  方法
}

核心代码如下

    
        
        
        
            
            
            
        
    


    
        select * from user left join sys_user_role sur on user.id = sur.userid  left join sys_role sr on sur.roleid = sr.id
    

以上可以发现,resultmap维护的就是sql 字段和 实体类的映射关系,这里要注意,因为常规开发中,我们经常列名是下划线命名,字段名是驼峰命名,为此,resultMap 在我们实际开发中实际上是一个很常用的标签。

4、注解开发

近年来随着注解开发越来越流行,mybatis也可以使用注解开发,不过这个只是个趋势,笔者自己的公司 实际上还是用的mapper 配置文件比较多,当时这种方式还是值得 学习了解。

4.1 使用注解完成简单增删改查

因为注解就不需要配置文件了,为此,调整mybatis sqlmap.xml mapper引入方式,调整后就能扫描到包下所有mapper

    

    
    
        
    

Iusermapper 新增注解方法

@CacheNamespace (implementation = RedisCache.class)//开启二级缓存
public interface IUserMapper {


    //添加用户
    @Insert("insert into user value(#{id},#{username})")
    public void addUser(User user);

    //更新用户
    @Update("update user set username=#{username} where id=#{id}")
    public void updateUser(User user);

    @Select("select * from user")
    public List selectUser();

    @Delete("delete from user where id=#{id}")
    public void deleteUser(User user);

    @Select("select * from user where id =#{id}")
    public User findUserById(Integer id);


}

测试类测试

  @Before
    public void befo() throws Exception{
        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        userMapper  = sqlSession.getMapper(IUserMapper.class);
        orderMapper=sqlSession.getMapper(IOrderMapper.class);
    }

    @Test
    public void addUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testadd");
        userMapper.addUser(user);
    }

    @Test
    public void updateUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testudpate");
        userMapper.updateUser(user);
    }

    @Test
    public void deleteUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testudpate");
        userMapper.deleteUser(user);
    }

    @Test
    public void selectUSer(){

        User user=new User();
        user.setId(1);
        user.setUsername("testudpate");
        userMapper.selectUser();
    }
4.2 使用注解实现复杂映射开发

实现复杂关系映射之前我们可以在映射⽂件中通过配置来实现,使⽤注解开发后,我们可以使⽤
@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置

  1. 一对一映射开发
    usermapper 新增注解查询方法
package com.example.mapper;

import com.example.pojo.Order;
import com.example.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface IOrderMapper {

    //查询订单的同时,查询所属的用户 一对一
    public List findOrderAndUSer();


    @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.example.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")
    public List findOrderAndUSerbyAnnotation();


    @Options(useCache = true)  //使用二级缓存
    @Select("select * from orders where uid = #{id}")
    public List findOrderByUId(Integer id);
}
  1. 一对多,多对多
public interface IUserMapper {


  //查询所有用户信息,同时查询所有用户所属订单信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "orderList",column = "id",javaType = List.class,many = @Many(
                select = "com.example.mapper.IOrderMapper.findOrderByUId"
            ))

    })
    public List findUserAndOrdersByAnnotation();


    //查询所有用户,同时查询所有用户关联角色信息
    @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.example.mapper.IRoleMapper.findRoleByid"
            ))

    })
    public List findUserAndRoleByAnnotation();

}

public interface IRoleMapper {

    @Select("select sys_role.* from sys_role,sys_user_role where sys_role.id=sys_user_role.roleid and sys_user_role.userid=#{id}")
    public List findRoleByid(Integer id);
}
5、缓存原理 5.1 缓存概念

mybatis 提供了缓存机制,以便部分不会频繁更新的热点数据能更高效的查询,减少与数据库的交互次数,针对不同的数据有不同的缓存级别

5.2 一级缓存

一级缓存是 会话级别的,一级缓存又叫查询缓存,因为对同一个sqlSession,查询完执行 commit操作,会将缓存中的数据清空。一级缓存底层是一个map, value 是对象地址,一级缓存默认开启

Override
public  List query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
 BoundSql boundSql = ms.getBoundSql(parameter);
 //创建缓存
 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
Override
public  List query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
 ...
 list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
 //这个主要是处理存储过程⽤的。
 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
 } else {
 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,
boundSql);
 }
 ...
// queryFromDatabase ⽅法
private  List queryFromDatabase(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql
boundSql) throws SQLException {
 List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
 try {
 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 } finally {
 localCache.removeObject(key);
 }
 localCache.putObject(key, list);
 if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
 }
 return list; }
}

private Map cache = new HashMap();
 @Override
 public void putObject(Object key, Object value) { cache.put(key, value);
}
5.3 二级缓存

二级缓存是namespace 级别的,多个sqlSession可以共享⼀个mapper中的⼆级缓存区域

开启二级缓存,需要在sqlmapConfig.xml中加入如下配置:



 
 

对应mapper文件中加入下面配置



针对每次查询都需要最新的数据sql,要设置成useCache=false,禁⽤⼆级缓存,直接从数据库中获取。在mapper的同⼀个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执⾏刷新缓存会出现脏读。设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使⽤缓存时如果⼿动修改数据库表中的查询数据会出现脏读。

mybatis除了默认实现,也对redis有个实现,pom 添加依赖

 
            org.mybatis.caches
            mybatis-redis
            1.0.0-beta2
 

resource 新增配置文件 redis.properties

host=localhost
port=6379
connectionTimeout=5000
password=
database=0

缓存注解调整,指定使用redis实现
@CacheNamespace (implementation = RedisCache.class)//开启二级缓存

mybatis-redis在存储数据的时候,是使⽤的hash结构,把cache的id作为这个hash
的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash
的field,需要缓存的内容直接使⽤SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责 对象
的序列化和反序列化;

底层结构默认开启级别存储额外处理
一级缓存hashmap会话级别对象地址(可以用==比较)
二级缓存hashmapnamespace对象拷贝存储对象实现序列号接口,sqlmapconfig.xml mapper文件要加额外配置,缓存才能生效,可以通过实现Cache 接口自定义缓存处理类(mybatis 有默认实现,也针对redis有个包)
6、插件机制

我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能

Mybati s作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的 动
态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象

MyBatis所允许拦截的⽅法如下:

执⾏器Executor (update、query、commit、rollback等⽅法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

6.1 插件原理

在四⼤对象创建的时候
1、每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返 回 target 包装后的对象
3、插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可 以
为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
                                                     Object object, BoundSql sql, InterceptorChain interceptorChain) {
        ParameterHandler parameterHandler =
                mappedStatement.getLang().createParameterHandler(mappedStatement, object, sql);
        parameterHandler = (ParameterHandler)
                interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
 }

 public Object pluginAll(Object target) {
     for (Interceptor interceptor : interceptors) {
         target = interceptor.plugin(target);
     }
     return target;
}

interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调⽤拦截器链中的拦截器依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象,如果我们想要拦截StatementHandler的prepare⽅法,那么可以这样定义插件:

package com.example.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;


@Intercepts({//注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤
这个拦截器
        @Signature(type = StatementHandler.class,//这是指拦截哪个接⼝
                method = "prepare",//接⼝内的哪个⽅法名
                args = {Connection.class,Integer.class})//这是拦截的⽅法的⼊参,按
顺序写到这,不要多也不要少,如果⽅法重载,可是要通过⽅法名和⼊参来确定唯⼀的
})
public class MyPlugin implements Interceptor {


    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强");
        return invocation.proceed(); //原方法执行
    }

    
    @Override
    public Object plugin(Object o) {

        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }


    
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件到的参数"+properties);
    }
}

在sqlmapConfig.xml 中配置插件

    
        
            
        
    

这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。
待准备⼯作做完后,MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory
创建 SqlSession。StatementHandler实例会在创建 SqlSession 的过程中被创建, StatementHandler实例创建完毕后,MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在StatementHandler相关⽅法被调⽤前执⾏。

6.2 自定义插件

Mybatis 插件接⼝-Interceptor

  • Intercept⽅法,插件的核⼼⽅法
  • plugin⽅法,⽣成target的代理对象
  • setProperties⽅法,传递插件所需参数

示例代码如上

public class Plugin implements InvocationHandler {
    private final Object target;
    private final Interceptor interceptor;
    private final Map, Set> signatureMap;

    public static Object wrap(Object target, Interceptor interceptor) {
        Map, Set> signatureMap = getSignatureMap(interceptor);
        Class type = target.getClass();
        Class[] interfaces = getAllInterfaces(type, signatureMap);
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

从mybatis 源码可以看到,Plugin实现了invocationHandler接口,通过warp 接口返回代理对象,在我们调用目标对象的invoke 方法时,会判断方法有没有被代理,如果被代理,会调用对应拦截器的intercept方法,否则调用原方法

6.3 常用插件 6.3.1 pagehelper插件

MyBati s可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿PageHelper是将分⻚的复杂操作进⾏封
装,使⽤简单的⽅式即可获得分⻚的相关数据
开发步骤:

  1. 导⼊通⽤PageHelper的坐标
  2. 在mybatis核⼼配置⽂件中配置PageHelper插件
  3. 测试分⻚数据获取

1、 导入坐标

 
 com.github.pagehelper
 pagehelper
 3.7.5
 
 
 com.github.jsqlparser
 jsqlparser
 0.9.1
 

2、配置pagehelper插件

 *
 
 
 
 
    @Test
    public void pageHelperTest(){

        PageHelper.startPage(1,1);
        List users = userMapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }

        PageInfo pageInfo=new PageInfo<>(users);
        System.out.println("总条数"+pageInfo.getTotal());
        System.out.println("总页数"+pageInfo.getPages());
        System.out.println("当前页"+pageInfo.getPageNum());
        System.out.println("每页显示条数"+pageInfo.getPageSize());
    }
6.3.2 通用mapper 插件

通⽤Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发⼈员不需要编写SQL,不需要
在DAO中增加⽅法,只要写好实体类,就能⽀持相应的增删改查⽅法

1、导入坐标


 tk.mybatis
 mapper
 3.1.2

2、配置插件


 
 
 
 
 
 
 
 

3、实体类设置主键

@Table(name = "user")
public class User implements Serializable {

    //public long serialVersionUID =1;

    @Id //对应主键 id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键生成策略
    private Integer id;

    @Column // 列名和实体类名字 不一样时候,使用
    private String username;

	
}

这里演示个别框架封装的方法,其他方法类似

    @Test
    public void mappperTest() throws Exception{

        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        UserMapper userMapper  = sqlSession.getMapper(UserMapper.class);

        User user=new User();
        user.setId(1);
         //(1)mapper基础接⼝
        //select 接⼝
        User user1 = userMapper.selectOne(user); //根据实体中的属性进⾏查询,只能有—个返回值
        List users = userMapper.select(null); //查询全部结果
        userMapper.selectByPrimaryKey(1); //根据主键字段进⾏查询,⽅法参数必须包含完整的主键属性,查询条件使⽤等号
        userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使⽤等号
        // insert 接⼝
        int insert = userMapper.insert(user); //保存⼀个实体,null值也会保存,不会使⽤数据库默认值
        int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存,会使⽤数据库默认值
        // update 接⼝
        int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段,null值会被更新
        // delete 接⼝
        int delete = userMapper.delete(user); //根据实体属性作为条件进⾏删除,查询条件 使⽤等号
        userMapper.deleteByPrimaryKey(1); //根据主键字段进⾏删除,⽅法参数必须包含完整的主键属性
        //(2)example⽅法

        Example example=new Example(User.class);
        example.createCriteria().andEqualTo("id",1);

        List users = userMapper.selectByExample(example);
        for (User user1 : users) {
            System.out.println(user1);
        }
    }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/306436.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号