Mapper
About 3850 wordsAbout 13 min
1984-01-24
BeetlSQL3的SQLManager提供了丰富的API操作数据库,但建议还是使用Mapper更为简单直接,跟容易维护
实现Mapper
推荐通过继承已有接口BaseMapper来实现Mapper
public interface UserMapper extends BaseMapper<UserEntity> {
}
BaseMapper 具备很多内置的CRUD方法,因此有了UserMapper后,同样可以不写任何SQL,可以轻易的完成常用操作。例如
//得到UserMapper接口的一个代理实现类,如果使用spring框架,则可以自动注入mapper
UserMapper mapper = sqlManager.getMapper(UserMapper.class);
List<UserEntity> list = mapper.all();
boolean isExist = mapper.exist(2);
UserEntity me = mapper.unique(1);
me.setName("newName");
mapper.updateById(me);
BaseMapper是BeetlSQL3提供的一个默认的类(你可以完成自己的BaseMapper,参考《定义自己的Mapper》),BaseMapper定义如下
/**
* BaseMapper.定义了一个Mapper接口,并内置了多个方法
* 开发者可以定义自己的"BaseMapper",并使用AutoMapper注解来申明这是内置的,beetlsql会调用其注解使用的接口
* @param <T> the generic type
* @author xiandafu
*/
public interface BaseMapper<T> {
/**
* 通用插入,插入一个实体对象到数据库,所以字段将参与操作,除非你使用ColumnIgnore注解
*SqlResource
* @param entity
*/
@AutoMapper(InsertAMI.class)
void insert(T entity);
/**
* 插入实体到数据库,对于null值不做处理
*
* @param entity
*/
@AutoMapper(InsertTemplateAMI.class)
void insertTemplate(T entity);
/**
* 批量插入实体。此方法不会获取自增主键的值,如果需要,建议不适用批量插入,适用
* <pre>
* insert(T entity,true);
* </pre>
*
* @param list
*/
@AutoMapper(InsertBatchAMI.class)
void insertBatch(List<T> list);
/**
* 根据主键更新对象,所以属性都参与更新。也可以使用主键ColumnIgnore来控制更新的时候忽略此字段
* @param entity
* @return
*/
@AutoMapper(UpdateByIdAMI.class)
int updateById(T entity);
/**
* 根据主键更新对象,只有不为null的属性参与更新
*
* @param entity
* @return
*/
@AutoMapper(UpdateTemplateByIdAMI.class)
int updateTemplateById(T entity);
/**
* 按照主键更新更新或插入,自增或者序列id自动赋值给entity
* @param entity 待更新/插入的实体对象
* @return 如果是插入操作,返回true,如果是更新,返回false
*/
@AutoMapper(UpsertAMI.class)
boolean upsert(T entity);
/**按照主键更新或插入,更新失败,会调用插入,属性为空的字段将不更新或者插入。自增或者序列id自动赋值给entity
* @param entity 待更新/插入的实体对象
* @return
*/
@AutoMapper(UpsertByTemplateAMI.class)
int upsertByTemplate(T entity);
/**
* 根据主键删除对象,如果对象是复合主键,传入对象本生即可
*
* @param key
* @return
*/
@AutoMapper(DeleteByIdAMI.class)
int deleteById(Object key);
/**
* 根据主键获取对象,如果对象不存在,则会抛出一个Runtime异常
*
* @param key
* @return
*/
@AutoMapper(UniqueAMI.class)
T unique(Object key);
/**
* 根据主键获取对象,如果对象不存在,返回null
*
* @param key
* @return
*/
@AutoMapper(SingleAMI.class)
T single(Object key);
/**
* 根据一批主键查询
* @param key
* @return
*/
@AutoMapper(SelectByIdsAMI.class)
List<T> selectByIds(List<?> key);
default boolean exist(Object key){
return this.getSQLManager().exist(this.getTargetEntity(),key);
}
/**
* 根据主键获取对象,如果在事物中执行会添加数据库行级锁(select * from table where id = ? for update),如果对象不存在,返回null
*
* @param key
* @return
*/
@AutoMapper(LockAMI.class)
T lock(Object key);
/**
* 返回实体对应的所有数据库记录
*
* @return
*/
@AutoMapper(AllAMI.class)
List<T> all();
/**
* 返回实体在数据库里的总数
*
* @return
*/
@AutoMapper(AllCountAMI.class)
long allCount();
/**
* 模板查询,返回符合模板得所有结果。beetlsql将取出非null值(日期类型排除在外),从数据库找出完全匹配的结果集
*
* @param entity
* @return
*/
@AutoMapper(TemplateAMI.class)
List<T> template(T entity);
/**
* 模板查询,返回一条结果,如果没有,返回null
*
* @param entity
* @return
*/
@AutoMapper(TemplateOneAMI.class)
<T> T templateOne(T entity);
/**
* 符合模板得个数
*
* @param entity
* @return
*/
@AutoMapper(TemplateCountAMI.class)
long templateCount(T entity);
/**
* 执行一个jdbc sql模板查询
*
* @param sql
* @param args
* @return
*/
@AutoMapper(ExecuteAMI.class)
List<T> execute(String sql, Object... args);
/**
* 执行一个更新的jdbc sql
*
* @param sql
* @param args
* @return
*/
@AutoMapper(ExecuteUpdateAMI.class)
int executeUpdate(String sql, Object... args);
@AutoMapper(GetSQLManagerAMI.class)
SQLManager getSQLManager();
/**
* 返回一个Query对象
*
* @return
*/
@AutoMapper(QueryAMI.class)
Query<T> createQuery();
/**
* 返回一个LambdaQuery对象
*
* @return
*/
@AutoMapper(LambdaQueryAMI.class)
LambdaQuery<T> createLambdaQuery();
/**
* 得到mapper的范型类
* @return
*/
@AutoMapper(GetTargetEntityAMI.class)
Class getTargetEntity();
}
BaseMapper提供了POJO常用的CRUD方法,每个BaseMapper的接口方法都是通过@AutoMapper注解申明一个实现类,实现类调用SQLManager相关得API。 关于此实现类,可以参考《扩展BeetlSQL3》
除了BaseMapper提供的方法外,你还可以为Mapper添加各种访问数据库的方法,说明如下
@Sql
使用@Sql注解可以提供一个Sql语句
@Sql("select * from sys_user where id = ?")
@Select
UserEntity queryUserById(Integer id);
当调用queryUserById的时候,会从@Sql上获得执行的sql,会从方法参数中按顺序获得sql的参数,底层会有如下类似调用
List list = sqlManager.execute(new SQLReady(sql,new Object[]{id}),UserEntity.class);
return list.get(0);
@Select注解标识这是一个查询操作,另外一个是@Update,和@BatchUpdate注解,标识更新操作和批量跟新,如果@Sql没有使用@Select和@Update,则默认是查询操作
如下执行更新操作
@Sql("update sys_user set name=? where id = ?")
@Update
int updateName(String name,Integer id);
当调用updateName方法的时候,相当于底层会有如下类似调用
return sqlManager.executeUpdate(new SQLReady(sql,new Object[]{name,id})
@Sql注解的实现org.beetl.sql.mapper.ready包下。
@Update @BatchUpdate
这俩个注解用来跟@Sql或者@Template结合,指明是更新操作或者是批量更新操作
@Sql("update sys_user set name=? where id = ?")
@Update
int updateName(String name,Integer id);
如果没有这个注解,则默认为查询操作
不同于2.x版本回自动推测类型,3.x需要显示的使用注解@Update,@BatchUpdate,@Select
@Template
Template Sql同JDBC SQL类似,主要区别是使用了@Template注解,其sql语句是模板sql。
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(Integer id);
@Template("select * from sys_user where id = #{join(ids)}")
List<UserEntity> getUserById(List<Integer> id);
参数名字将作为模板sql的变量名字,类似如下底层实现
Map paras = new HashMap();
map.put("id",id);
List list = sqlManager.execute(sql,paras,UserEntity.class)
参数名称
对于模板SQL来说,参数名称非常重要,如果是JDK8,且编译选项(或者maven配置)里启用了parameters参数,则BeetlSQL3能自动识别参数名。如果没有启用parameters,则需要使用@Param注解说明参数名称
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(@Param("id") Integer userId);
如果方法只有一个POJO类,那么该POJO的所有属性作为模板的参数
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(User user);
或者
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(@Root User user);
效果是一样的
@Root 修饰的POJO对象的属性,都可以直接在模板里访问
@Template("select * from sys_user where id = #{id} and status=#{myStatus}")
UserEntity getUserById(Integer myStatus,@Root User user);
当参数识别为root,那么调用sqlManger时候,传入的是
_root
为key,pojo为value的Map
注意,在maven打包的时候,同样需要启用paramters (maven不同版本,此配置方式略有不同,一般不需要配置parameter)
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- define the project compile level -->
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
参数返回值
参数可以是任意返回值,取决于于sql查询结果,不仅仅返回的pojo是Mapper定义的泛型,也可以是任意POJO.
@Sql("select * from sys_user where id = ?")
UserEntity queryUserById(Integer id);
@Sql("select * from sys_user where department_id = ?")
List<UserEntity> queryUserById(Integer id);
@Sql("select count(1) from sys_user)
int getCount();
@Template("update user set status=#{status} where id=#{id}")
@Update
int update( User user);
@Sql("select * from sys_deptmartment where id = ?")
DepartmentEntity queryDeptById(Inrteger id);
执行SQL文件
如果没有使用任何注解,BeetlSQL3则会寻找sql文件的sql片段作为sql
@SqlResource("user") /*寻找sql目录配置的文件夹下的user.md文件,参考spring集成*/
public interface UserMapper extends BaseMapper<UserEntity> {
/**
* 调用sql文件user.md#select,方法名即markdown片段名字
* @param name
* @return
*/
List<UserEntity> select(String name);
}
此时方法名就是sql片段名称
@SqlResource作用于注解和方法上,申明sql片段所在的文件,如果在Mapper上没有使用此注解,则会默认通过Mapper的泛型类来查找,规则是泛型类的名称首字母小写作为文件名,比如根据UserEntity转化成userEntity.md。 BeetlSQL3建议尽量使用@SqlResource
翻页查询
无论是JBDC SQL,还是模板SQL,还是SQL在文件里维护,翻页查询都有使用同样的规则,参数PageRequest 表示这个方法是翻页查询
Mapper方法得到返回值可以是PageResult,或者List(如果不关心总数,当且页数)
/**
* 翻页查询,调用user.md#pageQuery
* @param id
* @param pageRequest
* @return
*/
PageResult<UserEntity> pageQuery(Integer id, PageRequest pageRequest);
或者
@Sql("select * from sys_user where department_id = ?")
PageResult<UserEntity> queryDeptById(Integer id,PageRequest pageRequest);
或者
@Template("select #{page()} from sys_user where department_id = #{id}")
PageResult<UserEntity> queryTemplateDeptById(Integer id,PageRequest pageRequest);
参数列表里,pageRequest的位置不做要求,但最好在最后一个参数
@SqlProvider
类似@Sql一样,@SqlProvider指定了提供sql语句的类,BeetlSQL会调用同名方法获取sql语句和参数
@SqlProvider(provider= SelectUserProvider.class)
List<UserEntity> queryUserByCondition(String name);
SelectUserProvider定义如下
public static class SelectUserProvider{
/*与mapper方法同名,同参数*/
public SQLReady queryUserByCondition(String name){
SQLReady ready = null;
if(name==null){
String sql = "select * from sys_user where 1=1";
ready = new SQLReady(sql);
}else{
String sql = "select * from sys_user where name=?";
ready = new SQLReady(sql,name);
}
return ready;
}
}
BeetlSQL将会调用SelectUserProvider实例的同名同参数方法,获得SQLReady,执行查询。BeetlSQL底层代码类似如下代码
SelectUserProvider provider = BeanKit.newSingleInstance(SelectUserProvider.class);
SQLReady ready = provider.queryUserByCondition(name);
List<UserEntity> list = sqlManager.execute(ready,UserEntity.class)
@SqlProvider 注解有利于提供复杂的SQL,或者在原有@Sql不满足的情况,在不改变mapper方法情况下,使用@SqlProvider 提供新的SQL
@SqlProvider
的实现类是ProviderMapperExtBuilder
@SqlTemplateProvider
SqlTemplateProvider类似@SqlProvider,不同的是要求返回一个模板SQL语句
@SqlTemplateProvider(provider= SelectUserProvider.class)
List<UserEntity> queryUserByTemplateCondition(String name);
SelectUserProvider的queryUserByTemplateCondition定义如下
public class SelectUserProvider{
/*与mapper方法同名,同参数*/
public String queryUserByTemplateCondition(String name){
String sql = "select * from sys_user where 1=1";
if(name==null){
return sql;
}else{
return sql+" and name=#{name}";
}
}
}
BeetlSQL底层代码类似如下
SelectUserProvider provider = BeanKit.newSingleInstance(SelectUserProvider.class);
String sqlTemplate = provider.queryUserByTemplateCondition(name);
Map map = new HashMap();
map.put("name",name);
List<UserEntity> list = sqlManager.execute(sqlTemplate,UserEntity.class,map);
注解@SqlTemplateProvider
的实现类是ProviderMapperExtBuilder
@SpringData
注解@SpringData模仿了Spring Data中通过方法名解析出sql语句的功能,比如 findById, 则表示查询功能,且使用id属性。findByNameAndAgeOrderByIdDesc,则表示查询,根据name和age属性,且输出结果按照Id降序排序
要了解BeetlSQL3支持的所有Spring Data关键字,可以查看org.beetl.sql.mapper.springdata.SpringDataBuilder
,或者参考Spring Data文档 https://docs.spring.io/spring-data/jdbc/docs/2.0.1.RELEASE/reference/html/#jdbc.query-methods
@SpringData
List<UserEntity> queryByNameOrderById(String name);
BeetlSQL底层代码类似如下实现
Query<UserEntity> query = sqlManager.query(UserEntity.class);
query.andEq("name",name).asc("id");
List<UserEntity> list = query.select();
@SubQuery
注解@SubQuery 要求方法mapper方法返回LambdaQuery,不同于普通的LambdaQuery,此类实际上是其子类LambdaSubQuery,支持把子查询作为”表名“,以源码中的SubQueryCommonTest为例子说明
此想法和代码来源于 https://gitee.com/xiandafu/beetlsql/issues/I1WRHZ
@SqlResource("lambda")
public interface AnyMapper extends BaseMapper<User>{
/* 构造一个公共的子查询Lambda,由lambda#allUserInDepartment构成
*/
@SubQuery
public LambdaQuery<User> allUserInDepartment(Integer deptId);
}
如上allUserInDepartment方法返回一个LambdaQuery<User>
,lambda#allUserInDepartment提供了子查询语句,其内容如下
allUserInDepartment
===
select * from sys_user where department_id=#{deptId}
因此,BeetlSQL实际Lambda查询的时候,构成的sql其实如下
select * from (select * from sys_user where department_id=xxx) where .....
这样的好处是可以把复杂的查询转成简单的Lambda查询
AnyMapper anyMapper = sqlManager.getMapper(AnyMapper.class);
LambdaQuery<User> lambdaQuery = anyMapper.allUserInDepartment(1);
List<User> newList = lambdaQuery.andEq(User::getAge,42).select();
Assert.assertEquals(0,newList.size());
如上查询,对应的sql实际上是
┏━━━━━ Debug [sql.SELECT * FROM ( select * from sys_user where d...] ━━━
┣ SQL: SELECT * FROM ( select * from sys_user where department_id=? ) _t WHERE age = ?
┣ 参数: [1, 42]
┣ 位置: org.beetl.sql.core.mapper.SubQueryCommonTest.commonLambda(SubQueryCommonTest.java:32)
┣ 时间: 1ms
┣ 结果: [0]
┗━━━━━ Debug [sql.SELECT * FROM ( select * from sys_user where d...] ━━━
@InheritMapper
此为不常用的一个注解。当不仅仅满足BaseMapper提供的功能,项目提供了自己的BaseMapper的时候,比如CommonMapper,会遇到一个问题,就是自己CommonMapper的的公共sql查询方法,是查询CommonMapper指定的sql文件,还是继承接口的sql文件
BeetlSQL默认下是查询CommonMapper的指定文件,例子如下
@SqlResource("common")
public static interface CommonMapper<T> extends BaseMapper{
public List<T> implementByChild();
}
@SqlResource("user")
public static interface MyTestUserMapper extends CommonMapper<User>{
}
如上定义的Mapper,当调用MyTestUserMapper.implementByChild方法时候,默认寻找common.md文件
如果想寻找的是user.md文件,则需要使用@InheritMapper注解,调用MyTestUserMapper.implementByChild方法时候,寻找MyTestUserMapper上指定的sql文件
@SqlResource("common")
public static interface CommonMapper<T> extends BaseMapper{
@InheritMapper
public List<T> implementByChild();
}
@Call
用于存储过程调用,如下存储过程
CREATE DEFINER=`root`@`%` PROCEDURE `test`.`mytest`(IN s_count INT,OUT s_count2 varchar(10))
BEGIN
SELECT * from User;
SET s_count2='abc';
END
可以使用mapper,表示如下
@Call("call test.mytest(?,?)")
List<User> callSample(int id, @CallOutBean OutHolder outHolder);
对于输入参数,可以使用@CallParam() 表示,如下代码是等价的
@Call("call test.mytest(?,?)")
@Select
List<User> callSample(@CallParam(1) int id, @CallOutBean OutHolder outHolder);
CallParam包含了一个比选参数,存储过程输入参数索引,从1开始
如果存储过程有出参,则使用 @CallOutBean 指示的任意POJO对象封装,如上例子的OutHolder封装了返回结果,定义如下
@Data
public class OutHolder {
@CallParam(2)
String name;
}
因此对Mapper调用方法类似如下
OrderLogMapper orderLogMapper = sqlManager.getMapper(OrderLogMapper.class);
OutHolder outHolder = new OutHolder();
List<User> list = orderLogMapper.callSample(1,outHolder);
System.out.println(outHolder.getName());
System.out.println(list);
Call注解定义如下,申明使用CallBuilder 实现此Mapper方法,调用SQLManager.executeCall. 如果对其有兴趣,可以参考定
@Target({java.lang.annotation.ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Builder(CallBuilder.class) public @interface Call { String value() default ""; }
StreamData
如果方法返回StreamData,则会使用sqlManager的stream方法来查询,例子如下
@Sql("select * from sys_user where age!=?")
StreamData queryBySql(Integer age);
@Template("select * from sys_user where age!=#{age}")
StreamData queryByTemplate(Integer age);
//查询sql文件
StreamData streamTest();
StreamData提供foreach方法,遍历结果
StreamData<User> streamData = dao.queryBySql(99999);
streamData.foreach(user -> {
//处理user
});
需要注意的是,必须在事物上下文里遍历streamData,这是因为StreamData已经脱离了BeetlSQL,但包含了数据库链接用于加载数据,因此期望事物来自动关闭数据库链接
Default Method
Mapper接口可以通过default method来完成复杂的sql处理
public interface UserSelectMapper extends BaseMapper<UserEntity> {
default UserEntity selectById(Integer id ){
if(id<1000){
throw new UnsupportOperationExeceptioon("");
}
UserEntity userEntity = this.getSQLManager().unique(UserEntity.class,id);
return userEntity;
}
}
定义自己的BaseMapper
BeetlSQL提供了一个BaseMapper,实际上,你可以定制BaseMapper,或者提供任意多的"BaseMapper"
自定义Mapper可以继承BaseMapper,也可以不继承。
public interface MyBaseMapper<T> {
@AutoMapper(InsertAMI.class)
void insertOne(T entity);
@AutoMapper(UpdateByIdAMI.class)
int updateOneById(T entity);
}
如上MyBaseMapper,提供了insertOne,updateOneById方法,这俩个方法的解释都是通过@AutoMapper申明的类来解释的,比如insertOne是通过InsertAMI来实现的,这是BeetlSQL内置的,其定义如下
public class InsertAMI extends MapperInvoke {
@Override
public Object call(SQLManager sm, Class entityClass, Method m, Object[] args) {
int ret = sm.insert(args[0]);
return ret;
}
}
InsertAMI必须是MapperInvoke的子类,实现call方法即可,call方法有三个参数
- SQLManager sm BeetlSQL的基础核心类
- Class entityClass 翻新申明的类
- Method m 此mapper方法
- Object[] args 参数
MapperInvoke有非常多的子类,实际上本章的每个mapper实现,都是MapperInvoke的一个子类,如果想进一步了解如何定制Mapper,参考本书《扩展BeetlSQL3》
限制Java代码中SQL长度
无论使用@Sql还是@Template,都不建议sql过长,免得难以维护。 架构师可以限制SQL长度,配置文件beetlsql.properties 中如下设置
# Mapper方法 中@Sql或者@SqlTemplate 长度.设置-1不限制
MAPPER_SQL_MAX_LENGTH=-1
默认不限制@Sql和@Template 的sql长度