之前在做自己项目使用Mybatis的时候,一次偶然的机会看到了Mybatis Plus并使用了起来。不得不说,这个工具真的给开发提供了很大的便利性,推荐大家去试一下。特别是,它的ActiveRecord模式深深的吸引住了我:只要实体类继承一个类,并重写获取主键的值的方法,就可以使用实例对象去调用简单的增删改查方法。于是,我决定窥探一下Mybatis Plus工具的ActiveRecord模式。
官网:http://mp.baomidou.com/
Mybatis Plus(简称 MP)是一个 Mybatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
Active Record 是一种数据访问设计模式,它可以帮助你实现数据对象Object到关系数据库的映射。
应用Active Record 时,每一个类的实例对象唯一对应一个数据库表的一行(一对一关系)。你只需继承一个abstract Active Record 类就可以使用该设计模式访问数据库,其最大的好处是使用非常简单。
在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 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 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 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 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 map = new HashMap();
map.put("et", this);
map.put("ew", wrapper);
return SqlHelper.retBool(this.sqlSession().update(this.sqlStatement(SqlMethod.UPDATE), map));
}
public List 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 selectList(Wrapper wrapper) {
Map map = new HashMap();
map.put("ew", wrapper);
return this.sqlSession().selectList(this.sqlStatement(SqlMethod.SELECT_LIST), map);
}
public List 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 selectPage(Page page, Wrapper wrapper) {
Map map = new HashMap();
wrapper = SqlHelper.fillWrapper(page, wrapper);
map.put("ew", wrapper);
List tl = this.sqlSession().selectList(this.sqlStatement(SqlMethod.SELECT_PAGE), map, page);
page.setRecords(tl);
return page;
}
public Page selectPage(Page 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 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();
}
这里不做详细讲解,有兴趣的童鞋可以找相关资源进行学习。
(1)定义实体类(如果数据库表名、字段名跟实体类类名、属性名不符合默认转换规范,需要使用指定注解标明)。
(2)定义增删改查接口。
(3)在业务代码注入bean并使用。
其实,实体类对数据库的操作,本质上还是依赖实体类对应的CrudRepository接口。关键是,不同的实体类,所对应的CrudRepository接口的具体类也是不同的。所以,需要在实体类调用方法的时候,根据这个类找到对应的CrudRepository。
a.定义两个泛型,实体类的类型及其主键的类型。
b.定义一个获取主键值的抽象方法,强制子类覆盖。
c.定义一个map,用于将获取过的CrudRepository保存,避免重复获取影响性能。
完整代码
/**
* 具备增删查功能的实体父类
* @author z_hh
* @time 2018年11月10日
*/
/*
* T为实体自身类型,ID为实体主键类型
*/
public abstract class Model {
/**
* 用于获取容器中bean对象的上下文,由外部用Model.setApplicationContext方法传入
*/
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) {
Model.applicationContext = applicationContext;
}
/**
* 维护各个实体类对应的CrudRepository对象,避免重复调用applicationContext.getBean方法影响性能
*/
private Map> repositories = new HashMap<>();
@SuppressWarnings("unchecked")
private CrudRepository getRepository() {
// 1.获取实体对象对应的CrudRepository的bean名称,这里根据具体的命名风格来调整
String entityClassName = this.getClass().getSimpleName(),
beanName = entityClassName.substring(0, 1).toLowerCase() + entityClassName.substring(1) + "Dao";
CrudRepository crudRepository = repositories.get(beanName);
// 2.如果map中没有,从上下文环境获取,并放进map中
if (Objects.isNull(crudRepository)) {
crudRepository = (CrudRepository) 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();
}
@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("删除失败");
}
}
}