mybatisdynamicsql的简单介绍
本篇文章给大家谈谈mybatisdynamicsql,以及对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、1. 从selectList看Mybatis中一个sql 是如何执行的
- 2、MyBatis高级篇 - 动态SQL
- 3、如何开发自己的通用Mapper
- 4、6.mybatis里面的动态sql是怎么设定的,常用标签有那些以及其
- 5、Mybatis|SqlSession四大对象(二)
- 6、真正的Mybatis动态sql —MyBatis Dynamic SQL
1. 从selectList看Mybatis中一个sql 是如何执行的
Clinton Begin is a Senior Developer and Agile Mentor for ThoughtWorks Canada. He has been building enterprise applications for nine years based on platforms such as Java and .NET. Clinton has extensive experience with agile methodologies, persistence frameworks, and relational databases. He is the original creator of the iBATIS persistence framework, which he designed in response to the challenges faced by object oriented developers dealing with enterprise relational databases
由configuration解析,获取MappedStatement, variables
由SqlSession的executor 最终贺如执樱拍春行
一个典型的SQL, 由statement,parameter,RowBounds(分页参数)组成
如下:
configuration详见脊耐configuration章节
MappedStatement由XML/... StatementBuilder 产生
查询参数 详见paramter章节
可以看到BoundSql由sqlSource获取到. sqlSource有若干实现类, 包含我们常见的xml SqlSource, 还有annotation SqlSource
DynamicSqlSource 详见DynamicSqlSource 章节
分页参数,见 rowbounds章节
Mybatis Sql执行由Executor负责
org.apache.ibatis.executor.Executor
每个SqlSession需要注入executor
interceptorChain 参见interceptorChain
[img]MyBatis高级篇 - 动态SQL
MyBatis提供了一套动态SQL标签,协助我们完成 SQL 语句的拼接工作。我们在日常工作中经常需要对 SQL 进行拼接:入参循环遍历, where 条件拼接时的分隔符处理等等。
本文介绍以下几个较常用的动态SQL标签:
我们经常会遇到 where 条件中需要根据是否传入参数来确定是否添加条件。
为了解决上面所遇到的梁州裂问题,MyBatis 为我们提供了 where 标签。
foreach 用于遍历输入参数中集合对象。
我们的 Mapper 文件,在 select 或者是 where 经常会出现重复内容,我们可以把重复的内容抽取为一个 sql 片段,需要使用的地方,使用 include 标签就可以引橡闭入进来。迹缺
如何开发自己的通用Mapper
第一步,创建HsqldbMapperT
public interface HsqldbMapperT {
}
这个接口就是我们定义的通用Mapper,具体的接口方法在**第三步**写。其他的Mapper可以继承这个HsqldbMapperT。
第二部,创建HsqldbProvider
public class HsqldbProvider extends MapperTemplate {
//继承父类的方法
public HsqldbProvider(Class? mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
}
这个类是实际处派猛理操作的类,需要继承MapperTemplate,具体代码在**第四步**写。
第三步,在HsqldbMapperT中添加通用方法
这里以一个分页查询作为例子。 public interface HsqldbMapper { /** * 单表分页查询 * * @param object * @param offset * @param limit * @return */ @SelectProvider(type=HsqldbProvider.class,method = "dynamicSQL") List selectPage(@Param("entity") T object, @Param("offset") int offset, @Param("limit") int limit); }
返回结果为List,入参分别为查询条件和分页参数。在Mapper的接口方法中,当有多个入参的时候建议增加@Param注解,否则就得用param1,param2...来引用参数。
同时必须在方法上添加注解。查询使用SelectProvider,插入使用@InsertProvider,更新使用UpdateProvider,删除使用DeleteProvider。不同的Provider就相当于xml中不同的节点,如select,insert,update,delete。
因为这里是查询,所以要设置为SelectProvider,这4个Provider中的参数都一样,只塌羡大有type和method。
type必须设置为实际执行方法的HasqldbProvider.class,method必须设置为"dynamicSQL"。
通用Mapper处理的时候会根据type反射HasqldbProvider查找方法,而Mybatis的处理机制要求method必须是type类中只有一个入参,且返回值为String的方法。"dynamicSQL"方法定义在MapperTemplate中,该方法如下:
public String dynamicSQL(Object record) {
return "dynamicSQL";
}
这个方法只是为了满足Mybatis的要求,没有任何实际的作用。
第四步,在HsqldbProvider中实现真正处理Sql的方法
在这里有一点要求,那就是HsqldbProvider处理HsqldbMapperT中的方法时,方法名必须一样,因为这里需要通过反射来获取对应的方法,方法名一致一方面是为了减少开发人员的配置,另一方面和接口对应看起来更清晰。
除了方法名必须一样外,入参必须是MappedStatement ms,除此之外返回值可以是void或者SqlNode之一。
这里先讲一下通用Mapper的实现原理。通用Mapper目前是通过拦截器在通用方法第一次执行的时候去修改MappedStatement对象的SqlSource属性。而且只会执行一次,以后就和正常的方法没有任何区别。
使用Provider注解的这个Mapper方法,Mybatis本身会处理成ProviderSqlSource(一个SqlSource的实现类),由于之前的配置,这个ProviderSqlSource种的SQL是上面代码中返回的"dynamicSQL"。这个SQL没有任何作用,如果不做任何修改,执团竖行这个代码肯定会出错。所以在拦截器中拦截符合要求的接口方法,遇到ProviderSqlSource就通过反射调用如HsqldbProvider中的具体代码去修改原有的SqlSource。
最简单的处理Mybatis SQL的方法是什么?就是创建SqlNode,使用DynamicSqlSource,这种情况下我们不需要处理入参,不需要处理代码中的各种类型的参数映射。比执行SQL的方式容易很多。
有关这部分的内容建议查看通用Mapper的源码和Mybatis源码了解,如果不了解在这儿说多了反而会乱。
下面在HsqldbProvider中添加public SqlNode selectPage(MappedStatement ms)方法:
/**
* 分页查询
* @param ms
* @return
*/
public SqlNode selectPage(MappedStatement ms) {
Class? entityClass = getSelectReturnType(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
ListSqlNode sqlNodes = new ArrayListSqlNode();
//静态的sql部分:select column ... from table
sqlNodes.add(new StaticTextSqlNode("SELECT "
+ EntityHelper.getSelectColumns(entityClass)
+ " FROM "
+ tableName(entityClass)));
//获取全部列
ListEntityHelper.EntityColumn columnList = EntityHelper.getColumns(entityClass);
ListSqlNode ifNodes = new ArrayListSqlNode();
boolean first = true;
//对所有列循环,生成if test="property!=null"[AND] column = #{property}/if
for (EntityHelper.EntityColumn column : columnList) {
StaticTextSqlNode columnNode
= new StaticTextSqlNode((first ? "" : " AND ") + column.getColumn()
+ " = #{entity." + column.getProperty() + "} ");
if (column.getJavaType().equals(String.class)) {
ifNodes.add(new IfSqlNode(columnNode, "entity."+column.getProperty()
+ " != null and " + "entity."+column.getProperty() + " != '' "));
} else {
ifNodes.add(new IfSqlNode(columnNode, "entity."+column.getProperty() + " != null "));
}
first = false;
}
//将if添加到where
sqlNodes.add(new WhereSqlNode(ms.getConfiguration(), new MixedSqlNode(ifNodes)));
//处理分页
sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit}"),"offset==0"));
sqlNodes.add(new IfSqlNode(new StaticTextSqlNode(" LIMIT #{limit} OFFSET #{offset} "),"offset0"));
return new MixedSqlNode(sqlNodes);
}
注:对这段代码感觉吃力的,可以对比本页最下面**结构**部分XML形式的查看。
首先这段代码要实现的功能是这样,根据传入的实体类参数中不等于null(字符串也不等于'')的属性作为查询条件进行查询,根据分页参数进行分页。
先看这两行代码:
//获取实体类型
Class? entityClass = getSelectReturnType(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
首先获取了实体类型,然后通过setResultType将返回值类型改为entityClass,就相当于resultType=entityClass。
这里为什么要修改呢?因为默认返回值是T,Java并不会自动处理成我们的实体类,默认情况下是Object,对于所有的查询来说,我们都需要手动设置返回值类型。
对于insert,update,delete来说,这些操作的返回值都是int,所以不需要修改返回结果类型。
之后从ListSqlNode sqlNodes = new ArrayListSqlNode();代码开始拼写SQL,首先是SELECT查询头,在EntityHelper.getSelectColumns(entityClass)中还处理了别名的情况。
然后获取所有的列,对列循环创建if entity.property!=nullcolumn = #{entity.property}/if节点。最后把这些if节点组成的List放到一个where节点中。
这一段使用属性时用的是 entity. + 属性名,entity来自哪儿?来自我们前面接口定义处的Param("entity")注解,后面的两个分页参数也是。如果你用过Mybatis,相信你能明白。
之后在where节点后添加分页参数,当offset==0时和offset0时的分页代码不同。
最后封装成一个MixedSqlNode返回。
返回后通用Mapper是怎么处理的,这里贴下源码:
SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
setSqlSource(ms, dynamicSqlSource);
返回SqlNode后创建了DynamicSqlSource,然后修改了ms原来的SqlSource。
第五步,配置通用Mapper接口到拦截器插件中
plugins
plugin interceptor="com.github.abel533.mapper.MapperInterceptor"
!--================================================--
!--可配置参数说明(一般无需修改)--
!--================================================--
!--UUID生成策略--
!--配置UUID生成策略需要使用OGNL表达式--
!--默认值32位长度:@java.util.UUID@randomUUID().toString().replace("-", "")--
!--property name="UUID" value="@java.util.UUID@randomUUID().toString()"/--
!--主键自增回写方法,默认值MYSQL,详细说明请看文档--
property name="IDENTITY" value="HSQLDB"/
!--序列的获取规则,使用{num}格式化参数,默认值为{0}.nextval,针对Oracle--
!--可选参数一共3个,对应0,1,2,分别为SequenceName,ColumnName,PropertyName--
property name="seqFormat" value="{0}.nextval"/
!--主键自增回写方法执行顺序,默认AFTER,可选值为(BEFORE|AFTER)--
!--property name="ORDER" value="AFTER"/--
!--支持Map类型的实体类,自动将大写下划线的Key转换为驼峰式--
!--这个处理使得通用Mapper可以支持Map类型的实体(实体中的字段必须按常规方式定义,否则无法反射获得列)--
property name="cameHumpMap" value="true"/
!--通用Mapper接口,多个用逗号隔开--
property name="mappers" value="com.github.abel533.mapper.Mapper,com.github.abel533.hsqldb.HsqldbMapper"/
/plugin
/plugins
这里主要是**mappers**参数:
property name="mappers" value="com.github.abel533.mapper.Mapper,com.github.abel533.hsqldb.HsqldbMapper"/
多个通用Mapper可以用逗号隔开。
测试
接下来编写代码进行测试。
public interface CountryMapper extends MapperCountry,HsqldbMapperCountry {
}
在CountryMapper上增加继承HsqldbMapperCountry。
编写如下的测试:
@Test
public void testDynamicSelectPage() {
SqlSession sqlSession = MybatisHelper.getSqlSession();
try {
CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
//带查询条件的分页查询
Country country = new Country();
country.setCountrycode("US");
ListCountry countryList = mapper.selectPage(country, 0, 10);
//查询总数
Assert.assertEquals(1, countryList.size());
//空参数的查询
countryList = mapper.selectPage(new Country(), 100, 10);
Assert.assertEquals(10, countryList.size());
} finally {
sqlSession.close();
}
}
测试输出日志如下:
DEBUG [main] - == Preparing: SELECT ID,COUNTRYNAME,COUNTRYCODE FROM COUNTRY WHERE COUNTRYCODE = ? LIMIT ?
DEBUG [main] - == Parameters: US(String), 10(Integer)
TRACE [main] - == Columns: ID, COUNTRYNAME, COUNTRYCODE
TRACE [main] - == Row: 174, United States of America, US
DEBUG [main] - == Total: 1
DEBUG [main] - == Preparing: SELECT ID,COUNTRYNAME,COUNTRYCODE FROM COUNTRY LIMIT ? OFFSET ?
DEBUG [main] - == Parameters: 10(Integer), 100(Integer)
TRACE [main] - == Columns: ID, COUNTRYNAME, COUNTRYCODE
TRACE [main] - == Row: 101, Maldives, MV
TRACE [main] - == Row: 102, Mali, ML
TRACE [main] - == Row: 103, Malta, MT
TRACE [main] - == Row: 104, Mauritius, MU
TRACE [main] - == Row: 105, Mexico, MX
TRACE [main] - == Row: 106, Moldova, Republic of, MD
TRACE [main] - == Row: 107, Monaco, MC
TRACE [main] - == Row: 108, Mongolia, MN
TRACE [main] - == Row: 109, Montserrat Is, MS
TRACE [main] - == Row: 110, Morocco, MA
DEBUG [main] - == Total: 10
测试没有任何问题。
这里在来点很容易实现的一个功能。上面代码中:
countryList = mapper.selectPage(new Country(), 100, 10);
传入一个没有设置任何属性的Country的时候会查询全部结果。有些人会觉得传入一个空的对象不如传入一个null。我们修改测试代码看看结果。
执行测试代码后抛出异常:
Caused by: org.apache.ibatis.ognl.OgnlException: source is null for getProperty(null, "id")
为什么会异常呢,因为我们上面代码中直接引用的entity.property,在引用前并没有判断entity != null,因而导致了这里的问题。
我们修改HsqldbProvider中的selectPage方法,将最后几行代码进行修改,原来的代码:
//将if添加到where
sqlNodes.add(new WhereSqlNode(ms.getConfiguration(), new MixedSqlNode(ifNodes)));
修改后:
//增加entity!=null判断
IfSqlNode ifSqlNode = new IfSqlNode(new MixedSqlNode(ifNodes),"entity!=null");
//将if添加到where
sqlNodes.add(new WhereSqlNode(ms.getConfiguration(), ifSqlNode));
之后再进行测试就没有问题了。
6.mybatis里面的动态sql是怎么设定的,常用标签有那些以及其
1、动态SQL片段
通过SQL片段达到代码复用
!-- 动态条件分页查询 --
sql id="sql_count"
select count(*)
/sql
sql id="sql_select"
select *
/sql
sql id="sql_where"
from icp
dynamic prepend="where"
isNotEmpty prepend="and" property="name"
name like '%$name$%'
/isNotEmpty
isNotEmpty prepend="and" property="path"
path like '%path$%'
/isNotEmpty
isNotEmpty prepend="and" property="area_id"
area_id = #area_id#
/isNotEmpty
isNotEmpty prepend="and" property="hided"
hided = #hided#
/isNotEmpty
/dynamic
dynamic prepend=""
isNotNull property="_start"
isNotNull property="_size"
limit #_start#, #_size#
/isNotNull
/isNotNull
/dynamic
/冲判sql
select id="findByParamsForCount" parameterClass="map" resultClass="int"
include refid="sql_count"/
include refid="sql_where"/
/select
select id="findByParams" parameterClass="map" resultMap="icp.result_base"
散旁改include refid="sql_select"/
include refid="sql_where"/
/select
2、数字范围查询
所传参数名称是捏造所得,非数据库字段,比如_img_size_ge、_img_size_lt字段
isNotEmpty prepend="and" property="_img_size_ge"
![CDATA[
img_size 启纳= #_img_size_ge#
]]
/isNotEmpty
isNotEmpty prepend="and" property="_img_size_lt"
![CDATA[
img_size #_img_size_lt#
]]
/isNotEmpty
多次使用一个参数也是允许的
isNotEmpty prepend="and" property="_now"
![CDATA[
execplantime = #_now#
]]
/isNotEmpty
isNotEmpty prepend="and" property="_now"
![CDATA[
closeplantime = #_now#
]]
/isNotEmpty
3、时间范围查询
isNotEmpty prepend="" property="_starttime"
isNotEmpty prepend="and" property="_endtime"
![CDATA[
createtime = #_starttime#
and createtime #_endtime#
]]
/isNotEmpty
/isNotEmpty
Mybatis|SqlSession四大对象(二)
参数处理器负责为ParameterHandler的sql语句参数动态赋值。
这个接口中只有两个方法:
getParameterObject 方法用于读取参数。
setParameters用于对ParameterHandler的参数赋值。
参数处理器对象时在创建StatementHandler对象时同时被创建的,由configuration对象负责创建。
创建时传入三个对象:mappedStatement(执行SQL对应的配置信息)、parameterObject、boundSql。
注意:一个BoundSql对象,余灶代表了SQL语句的一次执行,而SqlSource对象的责任就是根据传入的参数对象动态计算出这个BoundSql,也就是说Mapper文件中的节点的计算是由SqlSource对象完成的。SqlSource最常用是实现类是DynamicSqlSource。
看看上述的实参10是如何添加到对应的SQL语句中的
在MyBatis中,使用动态代理模式,当dao.findByDeptNo(10)将要执行的时候,会被JVM进行拦截交给MyBatis中的代理实现类MapperProxy中的invoke方友毁派法。
最后交给ParameterHandler中setParameter方法,将参数交给对应占位符。
读取ParameterObject参数对象,然后用typeHandler对参数进行设置,而typeHandler里面需要对jdbcType和javaType进行处理,设置参数。所以使用好贺TypeHandler的时候完全可以控制如何设置SQL参数。
ResultSetHandler接口主要负责两件事:
真正的Mybatis动态sql —MyBatis Dynamic SQL
这个库是一个用于生成动态SQL语孝世句的框架。可以将它看氏漏作是一个类型安全的sQL模板库,它提供了对MyBatis3和Spring JDBC模板的额外支持。该库将生成供MyBatis或Spring使用的格式化的fuL LETE INET、SELECT和UPDATE语句。最常见的用例是生成可以直接由MyBatis使用的语句和一组数学参数。该库还将生成与Spring JDBC模板兼容的语句和参数对象。该库通过实现一个类似SQL的DSL来工作,该DSL创建一个对象,该对象包含完整的SQL语句和该语句所需的任何参数。
org.mybatis.dynamic.sql.SqlTable 表定义包括表的实际名称(包括适当的模式)。如果需要,可以在选择语句中应用表别名。你的Table应该继承SqlTable 类。
org. mybatiss .dynamic.sql. sqlcolumn 用于定义在库中使用的列。应该使用SqlTable中的构建器方法创建SqlColumns。列定义包括:
我们建议使用以下使用模式以提供最大的灵活性。这个模式允许您以“限定”或“非限定”的方式使用表和列名,这看起来像自然的SQL。例如,在下面的列中,一个列可以被称为 firstName 或 user.firstName 。
该库将创建用作 MyBatis mapper 输入的类。这些类包括生成的SQL,以及与生成的SQL匹配的参数集。这两者都是MyBatis所要求的。这些对象应该是 MyBatis mapper 方法的唯一参数。
(注意: MyBatis Dynamic SQL 不需要XML文件歼慎烂就能工作的很好,但并不意味着不支持XML,毕竟 **MyBatis **最初被设计为是一个 XML 驱动的框架。当你使用关联查询,需要复杂的映射,那么使用XML 与 MyBatis Dynamic SQL 结合起来或者是更好选择,你的XML或许只需要包含一些)
关于mybatisdynamicsql和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。