# Mapper
BeetlSQL3的SQLManager提供了丰富的API操作数据库,但建议还是使用Mapper更为简单直接,跟容易维护
## 实现Mapper
推荐通过继承接口BaseMapper来实现Mapper
```java
public interface UserMapper extends BaseMapper<UserEntity> {
}
```
BaseMapper 具备很多内置的CRUD方法,因此有了UserMapper后,同样可以不写任何SQL,可以轻易的完成常用操作。例如
```java
//得到UserMapper接口的一个代理实现类
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,参考《扩展BeetlSQL3》),BaseMapper定义如下
```java
/**
* 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注解申明一个实现类。关于此实现类,可以参考《扩展BeetlSQL3》
除了BaseMapper提供的方法外,你还可以为Mapper添加各种访问数据库的方法,说明如下
## @Sql
使用@Sql注解可以提供一个Sql语句
```java
@Sql("select * from sys_user where id = ?")
@Select
UserEntity queryUserById(Integer id);
```
当调用queryUserById的时候,会从@Sql上获得执行的sql,会从方法参数中按顺序获得sql的参数,底层会有如下类似调用
```java
List list = sqlManager.execute(new SQLReady(sql,new Object[]{id}),UserEntity.class);
return list.get(0);
```
@Select注解标识这是一个查询操作,另外一个是@Update,和@BatchUpdate注解,标识更新操作和批量跟新,如果@Sql没有使用@Select和@Update,则默认是查询操作
如下执行更新操作
```java
@Sql("update sys_user set name=? where id = ?")
@Update
int updateName(String name,Integer id);
```
当调用updateName方法的时候,相当于底层会有如下类似调用
```java
return sqlManager.executeUpdate(new SQLReady(sql,new Object[]{name,id})
```
> @Sql注解的实现org.beetl.sql.mapper.ready包下。
## @Update @BatchUpdate
这俩个注解用来跟@Sql或者@Template结合,指明是更新操作或者是批量更新操作
```java
@Sql("update sys_user set name=? where id = ?")
@Update
int updateName(String name,Integer id);
```
如果没有这个注解,则默认为查询操作
> 不同于2.x版本回自动推测类型,3.x需要显示的使用注解
## @Template
Template Sql同JDBC SQL类似,主要区别是使用了@Template注解
```java
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(Integer id);
```
参数名字将作为模板sql的变量名字,类似如下底层实现
```java
Map paras = new HashMap();
map.put("id",id);
List list = sqlManager.execute(sql,paras,UserEntity.class)
```
## 限制SQL长度
无论使用@Sql还是@Template,都不建议sql过长,免得难以维护。 架构师可以限制
## 参数名称
对于模板SQL来说,参数名称非常重要,如果是JDK8,且编译选项(或者maven配置)里启用了parameters参数,则BeetlSQL3能自动识别参数名。如果没有启用parameters,则需要使用@Param注解说明参数名称
```java
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(@Param("id") Integer userId);
```
如果方法只有一个POJO类,那么改POJO的所有属性作为模板的参数
```java
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(User user);
```
或者
```java
@Template("select * from sys_user where id = #{id}")
UserEntity getUserById(@Root User user);
```
效果是一样的
@Root 修饰的POJO对象的属性,都可以直接在模板里访问
```java
@Template("select * from sys_user where id = #{id} and status=#{myStatus}")
UserEntity getUserById(Integer myStatus,@Root User user);
```
注意,在maven打包的时候,同样需要启用paramters (maven不同版本,此配置方式略有不同)
```xml
<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.
```java
@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
```java
@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片段所在的文件,如果在POJO上没有使用此注解,则会默认通过泛型类来查找,规则是泛型类的名称首字母小写作为文件名, BeetlSQL3建议尽量使用@SqlResource
## 翻页查询
无论是JBDC SQL,还是模板SQL,还是SQL在文件里维护,翻页查询都有使用同样的规则,参数PageRequest 表示这个方法是翻页查询
Mapper方法得到返回值可以是PageResult,或者List(如果不关心总数,当且页数)
```java
/**
* 翻页查询,调用user.md#pageQuery
* @param id
* @param pageRequest
* @return
*/
PageResult<UserEntity> pageQuery(Integer id, PageRequest pageRequest);
```
或者
```java
@Sql("select * from sys_user where department_id = ?")
PageResult<UserEntity> queryDeptById(Integer id);
```
或者
```java
@Template("select #{page()} from sys_user where department_id = #{id}")
PageResult<UserEntity> queryTemplateDeptById(Integer id,PageRequest pageRequest);
```
参数列表里,pageRequest的位置不做要求,但最好在最后一个参数
## @SqlProvider
类似@Sql一样,@SqlProvider指定了提供sql语句的类,BeetlSQL会调用同名方法获取sql语句和参数
```java
@SqlProvider(provider= SelectUserProvider.class)
List<UserEntity> queryUserByCondition(String name);
```
SelectUserProvider定义如下
```java
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底层代码类似如下代码
```java
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
## @SqlTemplateProvider
SqlTemplateProvider类似@SqlProvider,不同的是要求返回一个模板SQL语句
```java
@SqlTemplateProvider(provider= SelectUserProvider.class)
List<UserEntity> queryUserByTemplateCondition(String name);
```
SelectUserProvider的queryUserByTemplateCondition定义如下
```java
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底层代码类似如下
```java
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);
```
## @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
```java
@SpringData
List<UserEntity> queryByNameOrderById(String name);
```
BeetlSQL底层代码类似如下实现
```java
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
```java
@SqlResource("lambda")
public interface AnyMapper extends BaseMapper<User>{
/* 构造一个公共的子查询Lambda,由lambda#allUserInDepartment构成
*/
@SubQuery
public LambdaQuery<User> allUserInDepartment(Integer deptId);
}
```
如上allUserInDepartment方法返回一个LambdaQuery<User>,lambda#allUserInDepartment提供了子查询语句,其内容如下
```sql
allUserInDepartment
===
select * from sys_user where department_id=#{deptId}
```
因此,BeetlSQL实际Lambda查询的时候,构成的sql其实如下
```java
select * from (select * from sys_user where department_id=xxx) where .....
```
这样的好处是可以把复杂的查询转成简单的Lambda查询
```java
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实际上是
```text
┏━━━━━ 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...] ━━━
```
## Default Method
Mapper接口可以通过default method来完成复杂的sql处理
```java
public interface UserSelectMapper extends BaseMapper<UserEntity> {
default UserEntity selectById(Integer id ){
UserEntity userEntity = this.getSQLManager().unique(UserEntity.class,id);
return userEntity;
}
}
```
## 定义自己的BaseMapper
BeetlSQL提供了一个BaseMapper,实际上,你可以定制BaseMapper,或者提供任意多的"BaseMapper"
```java
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内置的,其定义如下
```java
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》