MyBatis学习2
8 Mapper动态代理
在前面例子中自定义Dao接口实现类时发现一个问题:Dao的实现类其实并没有干什么实质性的工作,它仅仅就是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句,真正对DB进行操作的工作其实是由框架通过mapper中的SQL完成的。
所以,MyBatis框架就抛开了Dao的实现类,直接定位到映射文件mapper中的相应SQL语句,对DB进行操作。这种对Dao的实现方式称为Mapper的动态代理方式。
Mapper动态代理方式无需程序员实现Dao接口。接口是由MyBatis结合映射文件自动生成的动态代理实现的。
8.1 映射文件的namespace属性值
一般情况下,一个Dao接口的实现类方法使用的是同一个SQL映射文件中的SQL映射id。所以,MyBatis框架要求,将映射文件中mapper标签的namespace属性设为Dao接口的全类名,则系统会根据方法所属Dao接口,自动到相应namespace的映射文件中查找相关的SQL映射。
简单来说,通过接口名即可定位到映射文件mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chengzi.dao.IStudentDao">
<insert id="insertStudent" parameterType="com.chengzi.beans.Student">
insert into student(name,age,score) values (#{name},#{age},#{score})
</insert>
<insert id="insertStudentCachId" parameterType="com.chengzi.beans.Student">
insert into student(name,age,score) values (#{name},#{age},#{score})
<selectKey resultType="int" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
<delete id="deleteStudentById">
delete from student where id = #{id}
</delete>
<delete id="updateStudent">
update student set name = #{name},age=#{age},score=#{score} where id = #{id}
</delete>
<select id="selectAllStudents" resultType="com.chengzi.beans.Student">
select * from student
</select>
<select id="selectStudentById" resultType="com.chengzi.beans.Student">
select * from student where id = #{**}
</select>
<select id="selectStudentByName" resultType="com.chengzi.beans.Student">
select * from student where name like '%'#{ooo}'%'
</select>
<select id="selectStudentByMap" resultType="com.chengzi.beans.Student">
select * from student where id between #{studentId} and #{stu.id}
</select>
<resultMap id="studenteMapper" type="com.chengzi.beans.Student">
<id property="id" column="t_id"/>
<id property="name" column="t_name"/>
<id property="age" column="t_age"/>
</resultMap>
<select id="selectStudentById2" resultMap="studenteMapper">
select
t_id,t_name,t_age,score
from student where t_id = #{**}
</select>
</mapper>
8.2 修改日志输出控制文件
mapper的namespace修改了,则需要将日志输出控制文件中logger的输出对象进行修改。
#A根日志
#log4j.rootLogger=debug,console
#打印mybatis的sql日志更简单
log4j.logger.com.chengzi.dao.IStudentDao=debug,console
#A定义一个控制台日志附加器
log4j.appender.console=org.apache.log4j.ConsoleAppender
#控制输出到控制台使用的目录:Target,可以使用System.out或Sytem.err
log4j.appender.console.Target=System.out
#A指定附加器控制输出的日志
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#A定义附加器日志输出的格式,-表示打印的内容向左对齐
log4j.appender.console.layout.ConversionPattern=[log4j-demo] %-d{yyyy-MM-dd HH:mm:ss,SSS} [%-5p] %c:%l - %m%n
8.3 Dao接口方法名
MyBatis框架要求,接口中的方法名,与映射文件中相应的SQL标签的id值相同。系统会自动根据方法名到相应的映射文件中查找同名的SQL映射id。简单来说,通过方法名就可定位到映射文件mapper中相应的SQL语句。
public interface IStudentDao {
//插入
void insertStudent(Student studnet);
void insertStudentCachId(Student student);
//删除
void deleteStudentById(int id);
//改
void updateStudent(Student student);
//查询所有
List<Student> selectAllStudents();
List<Map<String, Object>> selectStudentMap();
//查询指定学生
Student selectStudentById(int id);
List<Student> selectStudentByMap(Map<String,Object> map);
//根据姓名查询
List<Student> selectStudentByName(String name);
Student selectStudentById2(int id);
}
8.4 Dao对象的获取
使用时,只需调用SqlSession的getMapper()方法,即可获取指定接口的实现类对象。该方法的参数为指定Dao接口类的class值。
public class MyTest2 {
private IStudentDao iStudentDao;
private SqlSession sqlSession;
@Before
public void before() throws IOException {
sqlSession = MybatisUtils.getSqlSession();
iStudentDao = sqlSession.getMapper(IStudentDao.class);
}
@Before
public void before() throws IOException {
sqlSession = MybatisUtils.getSqlSession();
iStudentDao = sqlSession.getMapper(IStudentDao.class);
sqlSession.commit();
}
@After
public void after() {
sqlSession.close();
}
//省略其他方法。。。
}
8.5 删除Dao实现类
由于通过调用Dao接口的方法,不仅可以从SQL映射文件中找到所要执行SQL语句,还可通过方法参数及返回值,将SQL语句的动态参数传入,将查询结果返回。所以,Dao的实现工作,完全可以由MyBatis系统自动根据映射文件完成。所以,Dao的实现类就不再需要了。
Dao实现对象是由JDK的Proxy动态代理自动生成的。
8.6 删除selectStudentMap()方法测试
MyBatis框架对于Dao查询的自动实现,底层只会调用selectOne()与selectList()方法。而框架选择方法的标准是测试类中用于接收返回值的对象类型。若接收类型为List,则自动选择selectList()方法;否则,自动选择selectOne()方法。
这里接收类型为Map,所以框架选择了selectOne()方法,会报错。所以这里需要删除这个selectStudentMap()方法的测试
8.7 返回类型为map
1、xml
<select id="selectStudentMap" resultType="map">
select * from student
</select>
2、接口
Map<String, Student> selectStudentMap();
3、测试类
@Test
public void testselectStudentMap() {
List<Map<String, Object>> list = iStudentDao.selectStudentMap();
for (Map<String, Object> l : list) {
Set<String> strings = l.keySet();
for (String k : strings){
System.out.println( k);
System.out.println(l.get(k));
}
}
}
使用场合:按照年龄统计人数:
select count(*),age from student group age;
9 多查询条件无法整体接收问题的解决
在实际开发中,表单中所给出的查询条件有时是无法将其封装为一个对象的,也就是说,查询方法只能携带多个参数,而不能携带将这多个参数进行封装的一个对象。对于这个问题,有两种解决方案。
(1)将这多个参数封装为一个Map
前面已经用过;
(2)多个参数逐个接收
对于mapper中的SQL语句,可以通过参数索引#{index}的方式逐个接收每个参数
<select id="selectStudentsByConditions" resultType="com.chengzi.beans.Student">
select * from student where name like '%' #{0} '%'
and age > #{1}
</select>
List<Student> selectStudentsByConditions(String n, int i);
@Test
public void testSelectStudentsByConditions() {
List<Student> students = iStudentDao.selectStudentsByConditions("n",20);
}
10 动态SQL
动态SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的SQL语句不同。若将每种可能的情况均逐一列出,对所有条件进行排列组合,将会出现大量的SQL语句。此时,可使用动态SQL来解决这样的问题。
动态SQL,即通过MyBatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。这里的条件判断使用的表达式为OGNL表达式。常用的动态SQL标签有iF、where、choose、foreach等。有一个有意思的发现是,MyBatis的动态SQL语句,与JSTL中的语句非常相似。
1、测试数据
2、注意事项
在mapper的动态SQL中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换为实体符号。否则,XML可能会出现解析出错问题。特别是对于小于号(<),在XML中是绝对不能出现的。否则,一定出错。
原符号 | 替换符号 |
---|---|
< | < ; |
<= | < ;= |
> | > ; |
>= | > ;= |
& | & ; |
’ | &apos ; |
" | " ; |
10.1 IF标签
对于该标签的执行,当test的值为true时,会将其包含的SQL片断拼接到其所在的SQL语句中。
本例实现的功能是:查询出满足用户提交查询条件的所有学生。用户提交的查询条件可以包含一个姓名的模糊查询,同时还可以包含一个年龄的下限。当然,用户在提交表单时可能两个条件均做出了设定,也可能两个条件均不做设定,也可以只做其中一项设定。
这引发的问题是,查询条件不确定,查询条件依赖于用户提交的内容。此时,就可使用动态SQL语句,根据用户提交内容对将要执行的SQL进行拼接。
1、xml
<select id="selectStudentIf" resultType="com.chengzi.beans.Student">
select * from student where 1=1
<if test="name != null and name != '' ">
and name like '%' #{name} '%'
</if>
<if test="age > 0 ">
and age > #{age}
</if>
</select>
2、dao接口
List<Student> selectStudentIf(Student student);
3、测试类
@Test
public void testSelectStudentsIf() {
Student student = new Student("",22,95);
List<Student> students = iStudentDao.selectStudentIf(student);
System.out.println(students);
}
10.2 WHERE标签
if标签的中存在一个比较麻烦的地方:需要在where后手工添加1=1的子句。因为,若where后的所有if条件均为false,而where后若又没有1=1子句,则SQL中就会只剩下一个空的where,SQL出错。所以,在where后,需要添加永为真子句1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。
1、xml
<select id="selectStudentWhere" resultType="com.chengzi.beans.Student">
select * from student
<where>
<if test="name != null and name != '' ">
and name like '%' #{name} '%'
</if>
<if test="age > 0 ">
and age > #{age}
</if>
<if test="score != null ">
and age > #{age}
</if>
</where>
</select>
注意:
使用<where/>标签,在有查询条件时,可以自动添加上where子句;没有查询条件时,不会添加where子句。
需要注意的是,第一个<if/>标签中的SQL片断,可以不包含and。不过,写上and也不错,系统会自动将多出的and去掉。
但其它<if/>中SQL片断的and,必须要求写上。否则SQL语句将拼接出错。
3、测试类
@Test
public void testSelectStudentsWhere() {
Student student = new Student("",22,95);
List<Student> students = iStudentDao.selectStudentWhere(student);
System.out.println(students);
}
4、运行结果
[log4j-demo] 2019-09-15 09:34:49,543 [DEBUG] com.chengzi.dao.IStudentDao.selectStudentWhere:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Preparing: select * from student WHERE age > ? and age > ?
[log4j-demo] 2019-09-15 09:34:49,573 [DEBUG] com.chengzi.dao.IStudentDao.selectStudentWhere:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - ==> Parameters: 22(Integer), 22(Integer)
[log4j-demo] 2019-09-15 09:34:49,590 [DEBUG] com.chengzi.dao.IStudentDao.selectStudentWhere:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:142) - <== Total: 5
[Student{id=1, name='张三', age=23, score=93.5}, Student{id=2, name='李四', age=24, score=94.5}, Student{id=3, name='王五', age=25, score=95.5}, Student{id=4, name='赵六', age=26, score=96.5}, Student{id=5, name='田七', age=27, score=97.5}]
10.3 CHOOSE标签
该标签中只可以包含< when />< otherwise/ >,可以包含多个< when/ >与一个< otherwise/>。它们联合使用,完成Java中的开关语句switch…case功能。本例要完成的需求是,若姓名不空,则按照姓名查询;若姓名为空,则按照年龄查询;若没有查询条件,则没有查询结果。
1、xml
<select id="selectStudentChoose" resultType="com.chengzi.beans.Student">
select * from student
<where>
<choose>
<when test="age != 0 ">
age >= #{age}
</when>
<when test="score != 0">
score >= #{score}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
对于<choose/>标签,其会从第一个<when/>开始逐个向后进行条件判断。若出现<when/>中的test属性值
为true的情况,则直接结果<choose/>标签,不再向后进行判断查找。若所有<when/>的test判断结果均
为false,则最后会执行<otherwise/>标签。
2、接口
List<Student> selectStudentChoose(Student student);
3、测试类
@Test
public void testSelectStudentChoose() {
Student student = new Student("",0,0);
List<Student> students = iStudentDao.selectStudentChoose(student);
System.out.println(students);
}
10.4 ForEach标签
10.4.1 遍历Array
< foreach />标签用于实现对于数组与集合的遍历。对其使用,需要注意:
(1)collection表示要遍历的集合类型,这里是数组,即array。
(2)open、close、separator为对遍历内容的SQL拼接。
本例实现的需求是,查询出id为1与3的学生信息。
1、xml
<select id="selectStudentForEachArray" resultType="com.chengzi.beans.Student">
select * from student
<if test="array != null and array.length > 0">
where id in
<foreach collection="array" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
动态SQL的判断中使用的都是OGNL表达式。OGNL表达式中的数组使用array表示,数组长度使用array.length表示
也就是说test后边只能写array,不能是其他
2、dao接口
List<Student> selectStudentForEachArray(Object[] studentIds);
3、测试类
@Test
public void testSelectStudentForEachArray() {
Object [] studentIds = new Object[] {1,3};
List<Student> students = iStudentDao.selectStudentForEachArray(studentIds);
System.out.println(students);
}
10.4.2 遍历List
1、xml
<select id="selectStudentForEachList" resultType="com.chengzi.beans.Student">
select * from student
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
2、dao接口
List<Student> selectStudentForEachList(List<Integer> studentIds);
3、测试类
@Test
public void testSelectStudentForEachList() {
List<Integer> studentIds = new ArrayList<>();
studentIds.add(1);
studentIds.add(3);
List<Student> students = iStudentDao.selectStudentForEachList(studentIds);
System.out.println(students);
}
10.4.3 遍历自定义类型的List
1、xml
<select id="selectStudentForEachListCustom" resultType="com.chengzi.beans.Student">
select * from student
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}
</foreach>
</if>
</select>
2、dao接口
List<Student> selectStudentForEachListCustom(List<Student> studentIds);
3、测试类
@Test
public void testSelectStudentForEachListCustom() {
List<Student> studentIds = new ArrayList<>();
Student student = new Student();
student.setId(1);
Student student2 = new Student();
student2.setId(3);
studentIds.add(student);
studentIds.add(student2);
List<Student> students = iStudentDao.selectStudentForEachListCustom(studentIds);
System.out.println(students);
}
10.5 SQL标签
< sql/>标签用于定义SQL片断,以便其它SQL标签复用。而其它标签使用该SQL片断,需要使用
< include/>子标签。该标签可以定义SQL语句中的任何部分,所以子标签可以放在动态SQL的任何位置。
1、xml
<sql id="selectHead">
select * from student
</sql>
<select id="selectStudentBySqlFragment" resultType="com.chengzi.beans.Student">
<include refid="selectHead"/>
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}
</foreach>
</if>
</select>
2、dao接口
List<Student> selectStudentBySqlFragment(List<Student> studentIds);
3、测试类
@Test
public void testSelectStudentBySqlFragment() {
List<Student> studentIds = new ArrayList<>();
Student student = new Student();
student.setId(1);
Student student2 = new Student();
student2.setId(3);
studentIds.add(student);
studentIds.add(student2);
List<Student> students = iStudentDao.selectStudentBySqlFragment(studentIds);
System.out.println(students);
}