解读Mybatis Plus的ActiveRecord,并实现Spring Data JPA版本的AR

版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/82959626

之前在做自己项目使用Mybatis的时候,一次偶然的机会看到了Mybatis Plus并使用了起来。不得不说,这个工具真的给开发提供了很大的便利性,推荐大家去试一下。特别是,它的ActiveRecord模式深深的吸引住了我:只要实体类继承一个类,并重写获取主键的值的方法,就可以使用实例对象去调用简单的增删改查方法。于是,我决定窥探一下Mybatis Plus工具的ActiveRecord模式。

一、Mybatis Plus介绍

官网:http://mp.baomidou.com/

Mybatis Plus(简称 MP)是一个 Mybatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

框架结构

framework

二、ActiveRecord实现原理

1、什么是ActiveRecord?

Active Record 是一种数据访问设计模式,它可以帮助你实现数据对象Object到关系数据库的映射。

应用Active Record 时,每一个类的实例对象唯一对应一个数据库表的一行(一对一关系)。你只需继承一个abstract Active Record 类就可以使用该设计模式访问数据库,其最大的好处是使用非常简单。

2、Mybatis Plus的ActiveRecord

在Mybatis-Plus中提供了ActiveRecord的模式,支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可实现基本 CRUD 操作,简单来说就是一个实体类继承Model类,并通过注解与数据库的表名进行关联,这样就可以通过实体类直接进行表的简单增删改查操作,这样也确实极大的方便了开发人员。

在MP中,我们可以这样使用AR模式:

(1)实体类继承Model类

(2)重写pkVal方法

(3)通过实体类直接进行表的简单增删改查操作

原理理解:

       简单来说Mybatis-plus是基于Mybatis的基础之上进行开发的,其基本操作还是一个Mapper操作中对应一条sql语句,通过参数和返回值来处理sql语句的执行结果。那样我们可以理解Mybatis-Plus的ActiveRecord其实就是Mybatis-Plus给我们提供一些简单的增删改查操作SQl语句的自动生成操作,可以参考博客mybtais-plus学习--BaseMapper提供的方法及SQL语句生成,在Mybatis提供的BaseMapper中默认提供了一些简单增删改查操作,其通过自动生成sql来初始化Mybatis的一些操作,其最终实现和我们直接基于Mybatis开发是一致的。

Model源码:

public abstract class Model<T extends Model> implements Serializable {
    private static final long serialVersionUID = 1L;

    public Model() {
    }

    @Transactional
    public boolean insert() {
        return SqlHelper.retBool(this.sqlSession().insert(this.sqlStatement(SqlMethod.INSERT_ONE), this));
    }

    @Transactional
    public boolean insertAllColumn() {
        return SqlHelper.retBool(this.sqlSession().insert(this.sqlStatement(SqlMethod.INSERT_ONE_ALL_COLUMN), this));
    }

    @Transactional
    public boolean insertOrUpdate() {
        if (StringUtils.checkValNull(this.pkVal())) {
            return this.insert();
        } else {
            return this.updateById() || this.insert();
        }
    }

    @Transactional
    public boolean deleteById(Serializable id) {
        return SqlHelper.delBool(this.sqlSession().delete(this.sqlStatement(SqlMethod.DELETE_BY_ID), id));
    }

    @Transactional
    public boolean deleteById() {
        if (StringUtils.checkValNull(this.pkVal())) {
            throw new MybatisPlusException("deleteById primaryKey is null.");
        } else {
            return this.deleteById(this.pkVal());
        }
    }

    @Transactional
    public boolean delete(String whereClause, Object... args) {
        return this.delete(Condition.create().where(whereClause, args));
    }

    @Transactional
    public boolean delete(Wrapper wrapper) {
        Map<String, Object> map = new HashMap();
        map.put("ew", wrapper);
        return SqlHelper.delBool(this.sqlSession().delete(this.sqlStatement(SqlMethod.DELETE), map));
    }

    @Transactional
    public boolean updateById() {
        if (StringUtils.checkValNull(this.pkVal())) {
            throw new MybatisPlusException("updateById primaryKey is null.");
        } else {
            Map<String, Object> map = new HashMap();
            map.put("et", this);
            return SqlHelper.retBool(this.sqlSession().update(this.sqlStatement(SqlMethod.UPDATE_BY_ID), map));
        }
    }

    @Transactional
    public boolean updateAllColumnById() {
        if (StringUtils.checkValNull(this.pkVal())) {
            throw new MybatisPlusException("updateAllColumnById primaryKey is null.");
        } else {
            Map<String, Object> map = new HashMap();
            map.put("et", this);
            return SqlHelper.retBool(this.sqlSession().update(this.sqlStatement(SqlMethod.UPDATE_ALL_COLUMN_BY_ID), map));
        }
    }

    @Transactional
    public boolean update(String whereClause, Object... args) {
        return this.update(Condition.create().where(whereClause, args));
    }

    @Transactional
    public boolean update(Wrapper wrapper) {
        Map<String, Object> map = new HashMap();
        map.put("et", this);
        map.put("ew", wrapper);
        return SqlHelper.retBool(this.sqlSession().update(this.sqlStatement(SqlMethod.UPDATE), map));
    }

    public List<T> selectAll() {
        return this.sqlSession().selectList(this.sqlStatement(SqlMethod.SELECT_LIST));
    }

    public T selectById(Serializable id) {
        return (Model)this.sqlSession().selectOne(this.sqlStatement(SqlMethod.SELECT_BY_ID), id);
    }

    public T selectById() {
        if (StringUtils.checkValNull(this.pkVal())) {
            throw new MybatisPlusException("selectById primaryKey is null.");
        } else {
            return this.selectById(this.pkVal());
        }
    }

    public List<T> selectList(Wrapper wrapper) {
        Map<String, Object> map = new HashMap();
        map.put("ew", wrapper);
        return this.sqlSession().selectList(this.sqlStatement(SqlMethod.SELECT_LIST), map);
    }

    public List<T> selectList(String whereClause, Object... args) {
        return this.selectList(Condition.create().where(whereClause, args));
    }

    public T selectOne(Wrapper wrapper) {
        return (Model)SqlHelper.getObject(this.selectList(wrapper));
    }

    public T selectOne(String whereClause, Object... args) {
        return this.selectOne(Condition.create().where(whereClause, args));
    }

    public Page<T> selectPage(Page<T> page, Wrapper<T> wrapper) {
        Map<String, Object> map = new HashMap();
        wrapper = SqlHelper.fillWrapper(page, wrapper);
        map.put("ew", wrapper);
        List<T> tl = this.sqlSession().selectList(this.sqlStatement(SqlMethod.SELECT_PAGE), map, page);
        page.setRecords(tl);
        return page;
    }

    public Page<T> selectPage(Page<T> page, String whereClause, Object... args) {
        return this.selectPage(page, Condition.create().where(whereClause, args));
    }

    public int selectCount(String whereClause, Object... args) {
        return this.selectCount(Condition.create().where(whereClause, args));
    }

    public int selectCount(Wrapper wrapper) {
        Map<String, Object> map = new HashMap();
        map.put("ew", wrapper);
        return SqlHelper.retCount((Integer)this.sqlSession().selectOne(this.sqlStatement(SqlMethod.SELECT_COUNT), map));
    }

    public SqlRunner sql() {
        return new SqlRunner(this.getClass());
    }

    protected SqlSession sqlSession() {
        return SqlHelper.sqlSession(this.getClass());
    }

    protected String sqlStatement(SqlMethod sqlMethod) {
        return this.sqlStatement(sqlMethod.getMethod());
    }

    protected String sqlStatement(String sqlMethod) {
        return SqlHelper.table(this.getClass()).getSqlStatement(sqlMethod);
    }

    protected abstract Serializable pkVal();
}

三、实现Spring Data JPA版本的AR

1、Spring Data JPA的基本使用

这里不做详细讲解,有兴趣的童鞋可以找相关资源进行学习。

(1)定义实体类(如果数据库表名、字段名跟实体类类名、属性名不符合默认转换规范,需要使用指定注解标明)。

(2)定义增删改查接口。

(3)在业务代码注入bean并使用。

2、定义抽象父类Model

其实,实体类对数据库的操作,本质上还是依赖实体类对应的CrudRepository接口。关键是,不同的实体类,所对应的CrudRepository接口的具体类也是不同的。所以,需要在实体类调用方法的时候,根据这个类找到对应的CrudRepository。

a.定义两个泛型,实体类的类型及其主键的类型。

b.定义一个获取主键值的抽象方法,强制子类覆盖。

c.定义一个map,用于将获取过的CrudRepository保存,避免重复获取影响性能。

完整代码

/**
 * 具备增删查功能的实体父类
 * @author z_hh
 * @time 2018年11月10日
 */
/*
 * T为实体自身类型,ID为实体主键类型
 */
public abstract class Model<T, ID> {
	
	/**
	 * 用于获取容器中bean对象的上下文,由外部用Model.setApplicationContext方法传入
	 */
	private static ApplicationContext applicationContext;
	
	public static void setApplicationContext(ApplicationContext applicationContext) {
		Model.applicationContext = applicationContext;
	}

	/**
	 * 维护各个实体类对应的CrudRepository对象,避免重复调用applicationContext.getBean方法影响性能
	 */
	private Map<String, CrudRepository<T, ID>> repositories = new HashMap<>();
	
	@SuppressWarnings("unchecked")
	private CrudRepository<T, ID> getRepository() {
		// 1.获取实体对象对应的CrudRepository的bean名称,这里根据具体的命名风格来调整
		String entityClassName = this.getClass().getSimpleName(),
			beanName = entityClassName.substring(0, 1).toLowerCase() + entityClassName.substring(1) + "Dao";
		CrudRepository<T, ID> crudRepository = repositories.get(beanName);
		// 2.如果map中没有,从上下文环境获取,并放进map中
		if (Objects.isNull(crudRepository)) {
			crudRepository = (CrudRepository<T, ID>) applicationContext.getBean(beanName);
			repositories.put(beanName, crudRepository);
		}
		// 返回
		return crudRepository;
	}
	
	/**
	 * 保存当前对象
	 * @return 保存后的当前对象
	 */
	@SuppressWarnings("unchecked")
	@Transactional
    public T save() {
		return getRepository().save((T) this);
    }
    
	/**
	 * 根据当前对象的id获取对象
	 * @return 查询到的对象
	 */
	@SuppressWarnings("unchecked")
	public T find() {
		return (T) getRepository().findById((ID) this.pkVal()).orElse(null);
    }
    
	/**
	 * 删除当前对象
	 */
    @SuppressWarnings("unchecked")
	@Transactional
    public void remove() {
    	getRepository().delete((T) this); 
    }
    
    protected abstract Serializable pkVal();
}

3、实体类继承Model并重写pkVal方法

4、编写Junit测试代码

@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentTest {

	@Autowired
	private StudentDao studentDao;
	
	@Autowired
	private ApplicationContext applicationContext;
	
	@Before
	public void init() {
		Model.setApplicationContext(applicationContext);
	}
	
	@Test
	public void testCrud() {
		Student student = new Student();
		student.setName("zhh");
		student.setSex(1);
		student.setMobile("13800138000");
		student.setBirthday(new Date());
		student.setAddress("广州市天河区");
//		studentDao.save(student);
		
		// 保存
		student.save();
		if (Objects.nonNull(student.getId())) {
			System.out.println("添加成功");
			System.out.println(student.toString());
		} else {
			System.out.println("添加失败");
		}
		
		// 查询
		Student student2 = new Student();
		student2.setId(student.getId());
		student2 = student2.find();
		if (Objects.nonNull(student2)) {
			System.out.println("查询成功");
			System.out.println(student2.toString());
		} else {
			System.out.println("查询失败");
		}
		
		// 删除
		student.remove();
		if (Objects.isNull(student.find())) {
			System.out.println("删除成功");
		} else {
			System.out.println("删除失败");
		}
		
	}
}

5、结果

本文内容到此结束了,有什么问题或者建议,欢迎在评论区进行探讨!

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/82959626