第 5 章 MybatisPlus ActiveRecord
1、ActiveRecord 简介
ActiveRecord(活动记录)
Active Record(活动记录 ),是一种领域模型模式,特点是一个模型类对应关系型数据库中的
一个表,而模型类的一个实例对应表中的 一行记录。
ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,
对于 ActiveRecord 往往只能感叹其优雅,所以 MP也在 AR 道路上进行了一定的探索
2、ActiveRecord 使用
在 MybatisPlus 中使用 AR 模式
我们仅仅需要让实体类继承 Model 抽象父类即可开启 AR 模式
/**
* @Author Oneby
* @Date 2021/4/18 17:53
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User extends Model<User> {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("username")
private String name;
private Integer age;
private String email;
}
测试代码:可以形象理解为 user
对象查询 user 对象(自产自销)
/**
* @Author Oneby
* @Date 2021/4/18 17:54
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisPlusCrudTest {
@Test
public void testAr() {
User user = new User();
user.setId(1L);
User retUser = user.selectById();
System.out.println(user);
}
}
程序运行结果:貌似和之前的 SQL 日志一样的嘛
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61f3fbb8] was not registered for synchronization because synchronization is not active
2021-04-24 14:00:58.167 INFO 12820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2021-04-24 14:00:58.311 INFO 12820 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1491676195 wrapping com.mysql.cj.jdbc.ConnectionImpl@3e7545e8] will not be managed by Spring
==> Preparing: SELECT id,username AS name,age,email FROM t_user WHERE id=?
==> Parameters: 1(Long)
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@61f3fbb8]
User(id=1, name=null, age=null, email=null)
MybatisPlus AR 模式的原理
Model
抽象父类中定义的 CRUD 方法的逻辑:① 执行 SqlSession sqlSession = sqlSession();
拿到数据库连接对象;② 执行 SqlHelper.retBool(sqlSession.Xxx())
方法执行 CRUD;③ 执行 closeSqlSession(sqlSession);
方法释放数据库连接
/**
* ActiveRecord 模式 CRUD
* <p>
* 必须存在对应的原始mapper并继承baseMapper并且可以使用的前提下
* 才能使用此 AR 模式 !!!
* </p>
*
* @param <T>
* @author hubin
* @since 2016-11-06
*/
public abstract class Model<T extends Model<?>> implements Serializable {
private static final long serialVersionUID = 1L;
private final transient Log log = LogFactory.getLog(getClass());
/**
* 插入(字段选择插入)
*/
public boolean insert() {
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retBool(sqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), this));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 插入 OR 更新
*/
public boolean insertOrUpdate() {
return StringUtils.checkValNull(pkVal()) || Objects.isNull(selectById(pkVal())) ? insert() : updateById();
}
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
public boolean deleteById(Serializable id) {
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retBool(sqlSession.delete(sqlStatement(SqlMethod.DELETE_BY_ID), id));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 根据主键删除
*/
public boolean deleteById() {
Assert.isFalse(StringUtils.checkValNull(pkVal()), "deleteById primaryKey is null.");
return deleteById(pkVal());
}
/**
* 删除记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
public boolean delete(Wrapper<T> queryWrapper) {
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
map.put(Constants.WRAPPER, queryWrapper);
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retBool(sqlSession.delete(sqlStatement(SqlMethod.DELETE), map));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 更新(字段选择更新)
*/
public boolean updateById() {
Assert.isFalse(StringUtils.checkValNull(pkVal()), "updateById primaryKey is null.");
// updateById
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
map.put(Constants.ENTITY, this);
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retBool(sqlSession.update(sqlStatement(SqlMethod.UPDATE_BY_ID), map));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 执行 SQL 更新
*
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
public boolean update(Wrapper<T> updateWrapper) {
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(2);
map.put(Constants.ENTITY, this);
map.put(Constants.WRAPPER, updateWrapper);
// update
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retBool(sqlSession.update(sqlStatement(SqlMethod.UPDATE), map));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 查询所有
*/
public List<T> selectAll() {
SqlSession sqlSession = sqlSession();
try {
return sqlSession.selectList(sqlStatement(SqlMethod.SELECT_LIST));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
public T selectById(Serializable id) {
SqlSession sqlSession = sqlSession();
try {
return sqlSession.selectOne(sqlStatement(SqlMethod.SELECT_BY_ID), id);
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 根据主键查询
*/
public T selectById() {
Assert.isFalse(StringUtils.checkValNull(pkVal()), "selectById primaryKey is null.");
return selectById(pkVal());
}
/**
* 查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
public List<T> selectList(Wrapper<T> queryWrapper) {
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
map.put(Constants.WRAPPER, queryWrapper);
SqlSession sqlSession = sqlSession();
try {
return sqlSession.selectList(sqlStatement(SqlMethod.SELECT_LIST), map);
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 查询一条记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
public T selectOne(Wrapper<T> queryWrapper) {
return SqlHelper.getObject(log, selectList(queryWrapper));
}
/**
* 翻页查询
*
* @param page 翻页查询条件
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
public <E extends IPage<T>> E selectPage(E page, Wrapper<T> queryWrapper) {
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(2);
map.put(Constants.WRAPPER, queryWrapper);
map.put("page", page);
SqlSession sqlSession = sqlSession();
try {
page.setRecords(sqlSession.selectList(sqlStatement(SqlMethod.SELECT_PAGE), map));
} finally {
closeSqlSession(sqlSession);
}
return page;
}
/**
* 查询总数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
public Integer selectCount(Wrapper<T> queryWrapper) {
Map<String, Object> map = CollectionUtils.newHashMapWithExpectedSize(1);
map.put(Constants.WRAPPER, queryWrapper);
SqlSession sqlSession = sqlSession();
try {
return SqlHelper.retCount(sqlSession.<Integer>selectOne(sqlStatement(SqlMethod.SELECT_COUNT), map));
} finally {
closeSqlSession(sqlSession);
}
}
/**
* 执行 SQL
*/
public SqlRunner sql() {
return new SqlRunner(getClass());
}
/**
* 获取Session 默认自动提交
*/
protected SqlSession sqlSession() {
return SqlHelper.sqlSession(getClass());
}
/**
* 获取SqlStatement
*
* @param sqlMethod sqlMethod
*/
protected String sqlStatement(SqlMethod sqlMethod) {
return sqlStatement(sqlMethod.getMethod());
}
/**
* 获取SqlStatement
*
* @param sqlMethod sqlMethod
*/
protected String sqlStatement(String sqlMethod) {
//无法确定对应的mapper,只能用注入时候绑定的了。
return SqlHelper.table(getClass()).getSqlStatement(sqlMethod);
}
/**
* 主键值
*/
protected Serializable pkVal() {
return (Serializable) ReflectionKit.getFieldValue(this, TableInfoHelper.getTableInfo(getClass()).getKeyProperty());
}
/**
* 释放sqlSession
*
* @param sqlSession session
*/
protected void closeSqlSession(SqlSession sqlSession) {
SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(getClass()));
}
}
SqlHelper
为 SQL 辅助类:主要用于获取 SessionFactory
、Session
、判断数据库操作是否成功等
/**
* SQL 辅助类
*
* @author hubin
* @since 2016-11-06
*/
public final class SqlHelper {
/**
* 获取SqlSessionFactory
*
* @param clazz 实体类
* @return SqlSessionFactory
* @since 3.3.0
*/
public static SqlSessionFactory sqlSessionFactory(Class<?> clazz) {
return GlobalConfigUtils.currentSessionFactory(clazz);
}
/**
* 获取Session
*
* @param clazz 实体类
* @return SqlSession
*/
public static SqlSession sqlSession(Class<?> clazz) {
return SqlSessionUtils.getSqlSession(GlobalConfigUtils.currentSessionFactory(clazz));
}
/**
* 判断数据库操作是否成功
*
* @param result 数据库操作返回影响条数
* @return boolean
*/
public static boolean retBool(Integer result) {
return null != result && result >= 1;
}
实际帮我们执行 SQL 操作的是 SqlSession
对象,SqlSession
接口中定义了各种 SQL 的基本操作,比如 selectOne()
、selectList()
、insert()
、update()
、delete()
等
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
public interface SqlSession extends Closeable {
/**
* Retrieve a single row mapped from the statement key.
* @param <T> the returned object type
* @param statement
* the statement
* @return Mapped object
*/
<T> T selectOne(String statement);
/**
* Retrieve a single row mapped from the statement key and parameter.
* @param <T> the returned object type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Mapped object
*/
<T> T selectOne(String statement, Object parameter);
/**
* Retrieve a list of mapped objects from the statement key.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @return List of mapped object
*/
<E> List<E> selectList(String statement);
/**
* Retrieve a list of mapped objects from the statement key and parameter.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter);
/**
* Retrieve a list of mapped objects from the statement key and parameter,
* within the specified row bounds.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* Eg. Return a of Map[Integer,Author] for selectMap("selectAuthors","id")
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param mapKey The property to use as key for each value in the list.
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, String mapKey);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param mapKey The property to use as key for each value in the list.
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
/**
* The selectMap is a special case in that it is designed to convert a list
* of results into a Map based on one of the properties in the resulting
* objects.
* @param <K> the returned Map keys type
* @param <V> the returned Map values type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param mapKey The property to use as key for each value in the list.
* @param rowBounds Bounds to limit object retrieval
* @return Map containing key pair data.
*/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement, Object parameter);
/**
* A Cursor offers the same results as a List, except it fetches data lazily using an Iterator.
* @param <T> the returned cursor element type.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return Cursor of mapped objects
*/
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
/**
* Retrieve a single row mapped from the statement key and parameter
* using a {@code ResultHandler}.
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, ResultHandler handler);
/**
* Retrieve a single row mapped from the statement
* using a {@code ResultHandler}.
* @param statement Unique identifier matching the statement to use.
* @param handler ResultHandler that will handle each retrieved row
*/
void select(String statement, ResultHandler handler);
/**
* Retrieve a single row mapped from the statement key and parameter using a {@code ResultHandler} and
* {@code RowBounds}.
*
* @param statement
* Unique identifier matching the statement to use.
* @param parameter
* the parameter
* @param rowBounds
* RowBound instance to limit the query results
* @param handler
* ResultHandler that will handle each retrieved row
*/
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
/**
* Execute an insert statement.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the insert.
*/
int insert(String statement);
/**
* Execute an insert statement with the given parameter object. Any generated
* autoincrement values or selectKey entries will modify the given parameter
* object properties. Only the number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the insert.
*/
int insert(String statement, Object parameter);
/**
* Execute an update statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the update.
*/
int update(String statement);
/**
* Execute an update statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the update.
*/
int update(String statement, Object parameter);
/**
* Execute a delete statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @return int The number of rows affected by the delete.
*/
int delete(String statement);
/**
* Execute a delete statement. The number of rows affected will be returned.
* @param statement Unique identifier matching the statement to execute.
* @param parameter A parameter object to pass to the statement.
* @return int The number of rows affected by the delete.
*/
int delete(String statement, Object parameter);
/**
* Flushes batch statements and commits database connection.
* Note that database connection will not be committed if no updates/deletes/inserts were called.
* To force the commit call {@link SqlSession#commit(boolean)}
*/
void commit();
/**
* Flushes batch statements and commits database connection.
* @param force forces connection commit
*/
void commit(boolean force);
/**
* Discards pending batch statements and rolls database connection back.
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
* To force the rollback call {@link SqlSession#rollback(boolean)}
*/
void rollback();
/**
* Discards pending batch statements and rolls database connection back.
* Note that database connection will not be rolled back if no updates/deletes/inserts were called.
* @param force forces connection rollback
*/
void rollback(boolean force);
/**
* Flushes batch statements.
* @return BatchResult list of updated records
* @since 3.0.6
*/
List<BatchResult> flushStatements();
/**
* Closes the session.
*/
@Override
void close();
/**
* Clears local session cache.
*/
void clearCache();
/**
* Retrieves current configuration.
* @return Configuration
*/
Configuration getConfiguration();
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
/**
* Retrieves inner database connection.
* @return Connection
*/
Connection getConnection();
}
我们来到 SqlSession
接口的默认实现类:DefaultSqlSession
,以查询方法为例
select()
方法:执行MappedStatement ms = configuration.getMappedStatement(statement);
获取MappedStatement
对象;再执行executor.query(ms, wrapCollection(parameter), rowBounds, handler);
执行 SQL 语句selectOne()
方法的底层实现逻辑为selectList()
方法selectList()
方法:执行MappedStatement ms = configuration.getMappedStatement(statement);
获取MappedStatement
对象;再执行executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
执行 SQL 语句
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
select(statement, null, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
AR 模式小结
AR 模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 Mybatis 对应的方法,类似于语法糖(语法糖是指计算机语言中添加的某种语法,这种语法对原本语言的功能并没有影响),可以更方便开发者使用,可以避免出错的机会,让程序可读性更好