【Java EE 学习 79 下】【动态SQL】【mybatis和spring的整合】
一、动态SQL
什么是动态SQL,就是在不同的条件下,sql语句不相同的意思,曾经在“酒店会员管理系统”中写过大量的多条件查询,那是在SSH的环境中,所以只能在代码中进行判断,以下是其中一个多条件查询的例子:
1 public Collection<Card> getCardsByMN(int requestPage, String cardId, 2 String userName, String typeofcredential, String sex, 3 String integral) { 4 int m=(requestPage-1)*PageSplitConfig.pageSize; 5 int n=PageSplitConfig.pageSize; 6 String sql="from Card where 1=1 "; 7 if(cardId!=null&&!"".equals(cardId.trim())){ 8 sql=sql+" and cardId='"+cardId.trim()+"'"; 9 } 10 if(userName!=null&&!"".equals(userName.trim())){ 11 sql=sql+" and userName='"+userName.trim()+"'"; 12 } 13 if(typeofcredential!=null&&!"".equals(typeofcredential.trim())&&!"''".equals(typeofcredential.trim())){ 14 sql=sql+" and credentialType.credentialTypeName='"+typeofcredential.trim()+"'"; 15 } 16 if(sex!=null&&!"".equals(sex.trim())&&!"不限".equals(sex.trim())){ 17 sql=sql+" and sex='"+sex+"'"; 18 } 19 if(integral!=null&&!"".equals(integral.trim())){ 20 sql=sql+" and integral='"+integral.trim()+"'"; 21 } 22 return this.cardDao.getAllCards(sql,m,n); 23 }
这是在Service方法中进行多条件的判断并最终形成多条件查询的SQL代码的方法,最终该SQL代码会被DAO执行。
和这个形式非常相似,Mybatis中对这种情况充分做了考虑,它使用配置文件的方式以XML标签的形式对以上的逻辑进行了重新描述。
在SSH环境中,由于Hibernate不支持动态SQL,所以只能在代码中进行SQL的动态拼接,这么做的弊端是显而易见的,而且该弊端在我的“酒店会员管理系统”中充分的暴露了出来。我的酒店会员管理系统中对对于日志的处理有两种形式,一种是多条件查询日志,一种是导出日志,这两个功能都涉及到了动态SQL拼接的问题,但是由于不是同一个Service方法,所以动态SQL的拼接必须重写两次,这就显得非常的麻烦,而且非常容易发生错误。但是如果使用Mybatis的话这种麻烦就会非常容易避免了。
1.动态SQL之多条件查询
这里的需求假设为:传入一个Student对象,程序会根据该对象属性值是否为空作为依据判断是否将该条件拼接到sql中;在映射文件中的配置方式和普通查询相比使用的标签都是select标签,但是里面需要使用mybatis中的逻辑判断标签进行判断:
<!-- 这里是动态查询的方法 --> <select id="selectAllByCondition" parameterType="com.kdyzm.domain.Student" resultMap="studentMap"> select * from student <where> <if test="id !=null"> and studentid=#{id} </if> <if test="name !=null"> and name=#{name} </if> <if test="age!=null"> and age=#{age} </if> <if test="password !=null"> and password=#{password} </if> </where> </select>
where标签是一个非常智能的标签,它能够通过不同的条件自动判断是否需要加上where关键字,如果你传入的对象中的属性值都为空,那么它就不会加上where关键字俄而且不需要使用where 1=1 这种写法;每个test中都需要加上and ,if标签也是智能标签,不赘述。
java代码:
1 String statement = "com.kdyzm.domain.Student.selectAllByCondition"; 2 Student student = new Student(); 3 student.setId(1); 4 // student.setAge(13); 5 List<Student> list = session.selectList(statement, student); 6 for (Student stu : list) { 7 System.out.println(stu); 8 }
2.动态SQL之多条件更新
其方法原理和多条件查询有诸多相似之处,不赘述。
1 <!-- 这里是动态更新的方法 --> 2 <update id="updateStudentByCandition" parameterType="com.kdyzm.domain.Student"> 3 update student 4 <set> 5 <!-- <if test="id!=null"> 6 studentid=#{id} 7 </if> --> 8 <if test="name!=null"> 9 name=#{name} 10 </if> 11 <if test="password!=null"> 12 password=#{password} 13 </if> 14 <if test="age!=null"> 15 age=#{age} 16 </if> 17 <if test="clazz!=null"> 18 clazz=#{clazz} 19 </if> 20 </set> 21 where studentid=#{id} 22 </update>
这里的动态sql的使用方法需要注意的事项:必须至少有一个字段不为空,否则就会报错;我本想着如果将以上代码中的注释部分放开的话就能够避免这种情况了,但是这只是理想状况,如果加上了id的判断,就会出错,原因未知。
二、mybatis和spring的整合
项目源代码:https://github.com/kdyzm/day79_2_ssi
mybatis框架的功能和hibernate功能相似,既然hibernate能够和spring整合并和struts框架整合之后形成所谓的SSH框架,那么mybatis框架能不能和spring、hibernate整合之后形成SSI框架呢?(Mybatis前身是Ibatis,到现在为止该框架中的很多包结构仍然采用ibatis的称呼,所以mybatis只是换了个名称而已,这里仍然使用SSI的称呼也只是从众而已),肯定是可以的。hibernate和Mybatis一样,就算没有spring也能够独立运行,但是既然要和spring整合了,那么Session对象的创建一定都交给了spring容器,这就需要一个中间的插件了,hibernate和spring整合的时候有一个org.springframework.orm-3.1.0.RELEASE.jar包,该jar包是hibernate和spring整合的关键,包括整合的过程中使用到的“本地会话工厂Bean”以及事务管理器,在该包中都有定义,spring发行版中会自带和hibernate整合相关的jar包,但是mybatis并没有这种待遇,mybatis为了能够为了和spring进行整合,它自己开发了和spring整合的jar包。这个jar包需要我们自己到网上下载。
和spring整合需要的jar包的下载地址:https://github.com/mybatis/spring
这里我是用了spring3.2的版本,有很多人习惯使用mybatis-spring-1.0.0版本的插件,但是在spring3.2版本下使用该插件的话会出现直接抛出某个异常,可以使用mybatis-spring-1.1.1版本的插件解决该异常,而且使用前者查询出来的结果需要强制类型转换,使用后者的时候就不需要进行强制类型转换。
mybatis和spring整合的过程与hibernate和spring整合的过程几乎完全相同,有了hibernate和spring整合的经验,mybatis和spring整合就太简单了。
第一步,加入所有需要的jar包。
使用的spring的版本:spring3.2
使用的数据库连接池:c3p0-0.9.5
使用的mybatis版本:mybatis3.1.1
使用的mybatis-spring版本:mybatis-spring-1.1.1
这里就不和struts2进行整合了,实际上和struts2的整合就完全没有必须要了,实际上和struts2的整合还是和spring的整合,略。
jar包一览图:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 c3p0-0.9.5.jar 2 com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar 3 com.springsource.net.sf.cglib-2.2.0.jar 4 com.springsource.org.aopalliance-1.0.0.jar 5 com.springsource.org.apache.commons.codec-1.3.0.jar 6 com.springsource.org.apache.commons.logging-1.1.1.jar 7 com.springsource.org.aspectj.tools-1.6.6.RELEASE.jar 8 com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar 9 com.springsource.org.quartz-1.6.2.jar 10 commons-lang-2.5.jar 11 log4j.jar 12 mchange-commons-java-0.2.9.jar 13 mybatis-3.1.1.jar 14 mybatis-spring-1.1.1.jar 15 mysql-connector-java-5.1.18-bin.jar 16 org.springframework.aop-3.1.0.RELEASE.jar 17 org.springframework.asm-3.1.0.RELEASE.jar 18 org.springframework.aspects-3.1.0.RELEASE.jar 19 org.springframework.beans-3.1.0.RELEASE.jar 20 org.springframework.context-3.1.0.RELEASE.jar 21 org.springframework.context.support-3.1.0.RELEASE.jar 22 org.springframework.core-3.1.0.RELEASE.jar 23 org.springframework.expression-3.1.0.RELEASE.jar 24 org.springframework.jdbc-3.1.0.RELEASE.jar 25 org.springframework.orm-3.1.0.RELEASE.jar 26 org.springframework.transaction-3.1.0.RELEASE.jar 27 org.springframework.web-3.1.0.RELEASE.jar 28 org.springframework.web.servlet-3.1.0.RELEASE.jar 29 slf4j-api-1.5.8.jar 30 slf4j-log4j12.jar
第二步,准备数据源需要的配置文件
这里使用jdbc.properties,将其放到classpath的根目录下
1 jdbc.username=root 2 jdbc.password=5a6f38 3 jdbc.url=jdbc:mysql://localhost:3306/mybatis 4 jdbc.driver=com.mysql.jdbc.Driver 5 6 #C3p0 Configuration 7 c3p0.minPoolSize=2 8 c3p0.maxPoolSize=10 9 c3p0.initPoolSize=2 10 c3p0.increment=2
第三步,不需要准备mybatis-config.xml配置文件
因为该配置文件中需要配置的所有内容都转移到了spring中的配置文件中进行配置了。
第四步,配置spring的配置文件,这里的配置过程和hibernate与spring整合的的配置过程几乎完全相同,不赘述。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/tx file:///D:\程序\java\Spring\spring-framework-4.2.1\spring-framework-4.2.1.RELEASE\schema/tx/spring-tx-2.5.xsd"> <context:component-scan base-package="com.kdyzm.dao"></context:component-scan> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 第一步还是配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="driverClass" value="${jdbc.driver}"></property> <property name="initialPoolSize" value="${c3p0.initPoolSize}"></property> <property name="minPoolSize" value="${c3p0.minPoolSize}"></property> <property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property> <property name="acquireIncrement" value="${c3p0.increment}"></property> </bean> <!-- 下一步,配置Session创建工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!-- <property name="configLocation" value="classpath:mybatis-config.xml"></property> --> <property name="mapperLocations"> <list> <value>classpath:com/kdyzm/domain/Student.xml</value> </list> </property> </bean> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg> </bean> </beans>
重点是黄色背景部分的配置,形式上和hibernate的和spring的整合过程一模一样,只是换了个类而已。
以上没有配置事务,但是已经能够进行正常测试了。
测试的过程略,想看直接到git中的相应项目中查看。
第五步:事务的配置
和hibernate和spring整合过程中事务的配置方式如出一辙,但是需要注意下面黄色背景部分的配置,事务管理器和hibernate使用的org.springframework.orm.hibernate3.HibernateTransactionManager是不相同的。
<!-- 事务的配置 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="save*" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/> <tx:method name="update*" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/> <tx:method name="new*" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/> <tx:method name="delete*" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/> <tx:method name="get*" read-only="true"/> <tx:method name="select*" read-only="true"/> <tx:method name="find*" read-only="true"/> <!-- 其它方法默认全部加上事务 --> <tx:method name="*" isolation="DEFAULT" read-only="false" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut expression="execution(* com.kdyzm.service.*.*(..))" id="txAop"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txAop"/> </aop:config>
三、进行测试即可,测试使用的DAO/Service等在项目中。
产生的疑问:保存Student对象的同时不能级联保存Clazz的问题,由于使用的是OGNL表达式,所以在前端页面中使用的Struts2的规则可以仿照这搬过来。
测试代码:
StudentService service=(StudentService) context.getBean("studentService"); ClazzService clazzService=(ClazzService) context.getBean("clazzService"); Student student=new Student(); student.setId(8); student.setName("小吧"); student.setPassword("xiaozhang"); student.setAge(120); Clazz clazz=clazzService.getClazzById(2); student.setClazz(clazz); int result=service.saveStudent(student); System.out.println("执行结果是:"+result);
配置文件:
<insert id="insertIntoStudent" parameterType="com.kdyzm.domain.Student"> insert into student(studentid,name,password,age,clazz) values(#{id},#{name},#{password},#{age},#{clazz.id}) </insert>
注意在这里的写法:如果直接写#{clazz},肯定直接报错,因为数据库中的字段类型是int类型,但是clazz在这里是Clazz对象。