# 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包下。 ## @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来说,参数名称非常重要,如果是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 status,@Root User user); ``` ## 参数返回值 参数可以是任意返回值,取决于于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,提供了insertOneupdateOneById方法,这俩个方法的解释都是通过@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》