SpringBoot学习笔记(五)——Spring Boot中使用MyBatis进阶
一、MyBatis配置文件详解
1.1、settings全局参数配置
mybatis框架运行时可以调整一些运行参数。比如,开启二级缓存,开启延迟加载等等。全局参数会影响mybatis的运行行为。
mybatis-settings的配置属性以及描述
| setting(设置) | Description(描述) | valid Values(验证值组) | Default(默认值) |
| cacheEnabled | 在全局范围内启用或禁用缓存配置 任何映射器在此配置下。 | true | false | TRUE |
| lazyLoadingEnabled | 在全局范围内启用或禁用延迟加载。禁用时,所有相关联的将热加载。 | true | false | TRUE |
| aggressiveLazyLoading | 启用时,有延迟加载属性的对象将被完全加载后调用懒惰的任何属性。否则,每一个属性是按需加载。 | true | false | TRUE |
| multipleResultSetsEnabled | 允许或不允许从一个单独的语句(需要兼容的驱动程序)要返回多个结果集。 | true | false | TRUE |
| useColumnLabel | 使用列标签,而不是列名。在这方面,不同的驱动有不同的行为。参考驱动文档或测试两种方法来决定你的驱动程序的行为如何。 | true | false | TRUE |
| useGeneratedKeys | 允许JDBC支持生成的密钥。兼容的驱动程序是必需的。此设置强制生成的键被使用,如果设置为true,一些驱动会不兼容性,但仍然可以工作。 | true | false | FALSE |
| autoMappingBehavior | 指定MyBatis的应如何自动映射列到字段/属性。NONE自动映射。 PARTIAL只会自动映射结果没有嵌套结果映射定义里面。 FULL会自动映射的结果映射任何复杂的(包含嵌套或其他)。 |
NONE,PARTIAL,FULL |
PARTIAL |
| defaultExecutorType | 配置默认执行人。SIMPLE执行人确实没有什么特别的。 REUSE执行器重用准备好的语句。 BATCH执行器重用语句和批处理更新。 |
SIMPLE,REUSE,BATCH |
SIMPLE |
| safeRowBoundsEnabled | 允许使用嵌套的语句RowBounds。 | true | false | FALSE |
| mapUnderscoreToCamelCase | 从经典的数据库列名A_COLUMN启用自动映射到骆驼标识的经典的Java属性名aColumn。 | true | false | FALSE |
| localCacheScope | MyBatis的使用本地缓存,以防止循环引用,并加快反复嵌套查询。默认情况下(SESSION)会话期间执行的所有查询缓存。如果localCacheScope=STATMENT本地会话将被用于语句的执行,只是没有将数据共享之间的两个不同的调用相同的SqlSession。 |
SESSION STATEMENT |
SESSION |
| dbcTypeForNull | 指定为空值时,没有特定的JDBC类型的参数的JDBC类型。有些驱动需要指定列的JDBC类型,但其他像NULL,VARCHAR或OTHER的工作与通用值。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
| lazyLoadTriggerMethods | 指定触发延迟加载的对象的方法。 | A method name list separated by commas | equals,clone,hashCode,toString |
| defaultScriptingLanguage | 指定所使用的语言默认为动态SQL生成。 | A type alias or fully qualified class name. |
org.apache.ibatis.scripting.xmltags .XMLDynamicLanguageDriver |
| callSettersOnNulls | 指定如果setter方法或map的put方法时,将调用检索到的值是null。它是有用的,当你依靠Map.keySet()或null初始化。注意(如整型,布尔等)不会被设置为null。 | true | false | FALSE |
| logPrefix | 指定的前缀字串,MyBatis将会增加记录器的名称。 | Any String | Not set |
| logImpl | 指定MyBatis的日志实现使用。如果此设置是不存在的记录的实施将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | Not set |
| proxyFactory | 指定代理工具,MyBatis将会使用创建懒加载能力的对象。 | CGLIB | JAVASSIST | CGLIB |
官方文档settings的例子:
<setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> settings>
示例:
这里设置MyBatis的日志输出到控制台:
mybatis:
type-aliases-package: com.zhangguo.mybatisdemo.entity #类型别名包的位置
mapper-locations: classpath:/mapper/*.xml #映射文件位置
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
结果:
1.2、typeAiases(别名)
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。
如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterType或resultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。
如下所示类型com.zhangguo.mybatis02.entities.Student会反复出现,冗余:
<?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.zhangguo.mybatis02.mapper.studentMapper"> <select id="selectStudentById" resultType="com.zhangguo.mybatis02.entities.Student"> SELECT id,name,sex from student where id=#{id} select> <select id="selectStudentsByName" parameterType="String" resultType="com.zhangguo.mybatis02.entities.Student"> SELECT id,name,sex from student where name like '%${value}%'; select> <insert id="insertStudent" parameterType="com.zhangguo.mybatis02.entities.Student"> insert into student(name,sex) VALUES(#{name},'${sex}') insert> <update id="updateStudent" parameterType="com.zhangguo.mybatis02.entities.Student"> update student set name=#{name},sex=#{sex} where id=#{id} update> <delete id="deleteStudent" parameterType="int"> delete from student where id=#{id} delete> mapper>
1.2.1.MyBatis默认支持的别名
|
别名 |
映射的类型 |
|
_byte |
byte |
|
_long |
long |
|
_short |
short |
|
_int |
int |
|
_integer |
int |
|
_double |
double |
|
_float |
float |
|
_boolean |
boolean |
|
string |
String |
|
byte |
Byte |
|
long |
Long |
|
short |
Short |
|
int |
Integer |
|
integer |
Integer |
|
double |
Double |
|
float |
Float |
|
boolean |
Boolean |
|
date |
Date |
|
decimal |
BigDecimal |
|
bigdecimal |
BigDecimal |
1.2.2.批量定义别名,扫描指定的包
定义单个别名的缺点很明显,如果项目中有很多别名则需要一个一个定义,且修改类型了还要修改配置文件非常麻烦,可以指定一个包,将下面所有的类都按照一定的规则定义成别名:
mybatis: #指定包名下所有的类被自动扫描并定义默认别名, #mybatis会自动扫描包中的pojo类,自动定义别名,别名就是类名(首字母大写或小写都可以) type-aliases-package: com.zhangguo.mybatisdemo.entity #类型别名包的位置 mapper-locations: classpath:/mapper/*.xml #映射文件位置 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
如果com.zhangguo.mybatis02.entities包下有一个名为Student的类,则使用别名时可以是:student,或Student。
你一定会想到当两个名称相同时的冲突问题,可以使用注解解决
解决方法:
1.3、typeHandlers(类型处理器)
mybatis中通过typeHandlers完成jdbc类型和java类型的转换。
通常情况下,mybatis提供的类型处理器满足日常需要,不需要自定义.
mybatis支持类型处理器:
|
类型处理器 |
Java类型 |
JDBC类型 |
|
BooleanTypeHandler |
Boolean,boolean |
任何兼容的布尔值 |
|
ByteTypeHandler |
Byte,byte |
任何兼容的数字或字节类型 |
|
ShortTypeHandler |
Short,short |
任何兼容的数字或短整型 |
|
IntegerTypeHandler |
Integer,int |
任何兼容的数字和整型 |
|
LongTypeHandler |
Long,long |
任何兼容的数字或长整型 |
|
FloatTypeHandler |
Float,float |
任何兼容的数字或单精度浮点型 |
|
DoubleTypeHandler |
Double,double |
任何兼容的数字或双精度浮点型 |
|
BigDecimalTypeHandler |
BigDecimal |
任何兼容的数字或十进制小数类型 |
|
StringTypeHandler |
String |
CHAR和VARCHAR类型 |
|
ClobTypeHandler |
String |
CLOB和LONGVARCHAR类型 |
|
NStringTypeHandler |
String |
NVARCHAR和NCHAR类型 |
|
NClobTypeHandler |
String |
NCLOB类型 |
|
ByteArrayTypeHandler |
byte[] |
任何兼容的字节流类型 |
|
BlobTypeHandler |
byte[] |
BLOB和LONGVARBINARY类型 |
|
DateTypeHandler |
Date(java.util) |
TIMESTAMP类型 |
|
DateOnlyTypeHandler |
Date(java.util) |
DATE类型 |
|
TimeOnlyTypeHandler |
Date(java.util) |
TIME类型 |
|
SqlTimestampTypeHandler |
Timestamp(java.sql) |
TIMESTAMP类型 |
|
SqlDateTypeHandler |
Date(java.sql) |
DATE类型 |
|
SqlTimeTypeHandler |
Time(java.sql) |
TIME类型 |
|
ObjectTypeHandler |
任意 |
其他或未指定类型 |
|
EnumTypeHandler |
Enumeration类型 |
VARCHAR-任何兼容的字符串类型,作为代码存储(而不是索引)。 |
二、MyBatis输入输出映射
2.1、输入映射
通过parameterType指定输入参数的类型,类型可以是简单类型、HashMap、POJO的包装类型。
Mybatis的配置文件中的select,insert,update,delete有一个属性parameter来接收mapper接口方法中的参数。可以接收的类型有简单类型和复杂类型,但是只能是一个参数。这个属性是可选的,因为Mybatis可以通过TypeHandler来判断传入的参数类型,默认值是unset。
2.1.1、基本类型
各种java的基本数据类型。常用的有int、String、Data等
接口:
/** * 根据学生编号获得学生对象 */ Student selectStudentById(int id);
映射:
<select id="selectStudentById" resultType="Student" parameterType="int"> SELECT id,name,sex from student where id=#{id} select>
测试:
/** * Method: selectStudentById(int id) */ @Test public void testSelectStudentById() throws Exception { Student entity=dao.selectStudentById(1); System.out.println(entity); Assert.assertNotNull(entity); }
结果:
用#{变量名}来取值,这里的变量名是任意的,可以用value或者是其它的什么值,这里用id是为了便于理解,并不存在什么对应关系的。因为java反射主只能够得到方法参数的类型,而无从知道参数的名字的。当在动态sql中的if语句中的test传递参数时,就必须要用_parameter来传递参数了(OGNL表达式),如果你传入id就会报错。
2.1.2、多个参数
(一)、直接使用参数名称
该方法在低版本中可能出现错误
(二)、使用Map
接口:
/** * 根据学生姓名或性别获得学生集合 */ ListselectStudentsByNameOrSex(Map params);
映射:
<select id="selectStudentsByNameOrSex" resultType="student"> SELECT id,name,sex from student where name like '%${name}%' or sex=#{sex}; select>
测试:
/** * Method: ListselectStudentsByNameOrSex(Map */ @Test public void selectStudentsByNameOrSex() throws Exception { Mapparams); params=new HashMap (); params.put("name","Candy"); params.put("sex","girl"); List students=dao.selectStudentsByNameOrSex(params); System.out.println(students); Assert.assertNotNull(students); }
结果:
(三)、注解参数名称:
接口:
/** * 根据学生姓名或性别获得学生集合 */ ListselectStudentsByNameOrSex(@Param("realname") String name,@Param("sex") String sex);
映射:
<select id="selectStudentsByNameOrSex" resultType="student"> SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex}; select>
测试:
/** * Method: selectStudentsByNameOrSex(String name,String sex) */ @Test public void testSelectStudentsByNameOrSex() throws Exception { Liststudents=dao.selectStudentsByNameOrSex("C","boy"); System.out.println(students); Assert.assertNotNull(students); }
结果:
2.1.3、POJO对象
POJO是Plain OrdinaryJava Object的缩写,但是它通指没有使用Entity Beans的普通java对象,可以把POJO作为支持业务逻辑的协助类。
各种类型的POJO,取值用#{属性名}。这里的属性名是和传入的POJO中的属性名一一对应。
接口:
/** * 添加学生 */ int insertStudent(Student entity);
映射:
<insert id="insertStudent" parameterType="student"> insert into student(name,sex) VALUES(#{name},'${sex}') insert>
测试:
/** * Method: insertStudent */ @Test public void testInsertStudent() throws Exception { Student entity=new Student(); entity.setName("张明"); entity.setSex("boy"); Assert.assertEquals(1,dao.insertStudent(entity)); }
结果:
如果要在if元素中测试传入的user参数,仍然要使用_parameter来引用传递进来的实际参数,因为传递进来的User对象的名字是不可考的。如果测试对象的属性,则直接引用属性名字就可以了。
测试user对象:
<if test="_parameter!= null">
测试user对象的属性:
<if test="name!= null">
如果对象中还存在对象则需要使用${属性名.属性.x}方式访问
2.1.4、Map
具体请查看2.1.2节。
传入map类型,直接通过#{keyname}就可以引用到键对应的值。使用@param注释的多个参数值也会组装成一个map数据结构,和直接传递map进来没有区别。
mapper接口:
int updateByExample(@Param("user") User user, @Param("example") UserExample example);
sql映射:
<update id="updateByExample" parameterType="map" > update tb_user set id = #{user.id}, ... <if test="_parameter != null" > <include refid="Update_By_Example_Where_Clause" /> if> update>
注意这里测试传递进来的map是否为空,仍然使用_parameter
2.1.5、集合类型
可以传递一个List或Array类型的对象作为参数,MyBatis会自动的将List或Array对象包装到一个Map对象中,List类型对象会使用list作为键名,而Array对象会用array作为键名。集合类型通常用于构造IN条件,sql映射文件中使用foreach元素来遍历List或Array元素。
假定这里需要实现多删除功能,示例如下:
接口:
/** * 删除多个学生通过编号 */ int deleteStudents(Listids);
映射:
<delete id="deleteStudents"> delete from student where id in <foreach collection="list" item="id" open="(" separator="," close=")"> #{id} foreach> delete>
collection这里只能是list
测试:
/** * Method: deleteStudents */ @Test public void testDeleteStudents() throws Exception { Listids=new ArrayList (); ids.add(10); ids.add(11); Assert.assertEquals(2,dao.deleteStudents(ids)); }
结果:
当然查询中也可以这样使用
public List<XXXBean> getXXXBeanList(List<String> list); <select id="getXXXBeanList" resultType="XXBean"> select 字段... from XXX where id in <foreach item="item" index="index" collection="list" open="(" separator="," close=")"> #{item} foreach> select> foreach 最后的效果是select 字段... from XXX where id in ('1','2','3','4')
对于单独传递的List或Array,在SQL映射文件中映射时,只能通过list或array来引用。但是如果对象类型有属性的类型为List或Array,则在sql映射文件的foreach元素中,可以直接使用属性名字来引用。
mapper接口:
ListselectByExample(UserExample example);
sql映射文件:
<where> <foreach collection="oredCriteria" item="criteria" separator="or"> <if test="criteria.valid"> where>
在这里,UserExample有一个属性叫oredCriteria,其类型为List,所以在foreach元素里直接用属性名oredCriteria引用这个List即可。
item="criteria"表示使用criteria这个名字引用每一个集合中的每一个List或Array元素。
使用数组与可变参数
ListfindByIds(int... ids);
sql映射文件:
<select id="findByIds" resultType="Category"> SELECT id,name from category where id in <foreach collection="array" open="(" close=")" separator=","> #{ids} foreach> select>
2.2、输出映射
输出映射主要有两种方式指定ResultType或ResultMap,现在分别介绍一下:
2.2.1、ResultType
使用ResultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和POJO中的属性名全部不一致,没有创建POJO对象。
只要查询出来的列名和POJO中的属性有一个一致,就会创建POJO对象。
(一)、输出简单类型
接口:
/** * 获得学生总数 * */ long selectStudentsCount();
映射:
<select id="selectStudentsCount" resultType="long"> SELECT count(*) from student select>
测试:
/** * Method: selectStudentsCount() */ @Test public void testSelectStudentsCount() throws Exception { Assert.assertNotEquals(0,dao.selectStudentsCount()); }
结果:
查询出来的结果集只有一行一列,可以使用简单类型进行输出映射。
(二)、输出POJO对象和POJO列表
不管是输出的POJO单个对象还是一个列表(List中存放POJO),在mapper.xml中ResultType指定的类型是一样的,但方法返回值类型不一样。
输出单个POJO对象,方法返回值是单个对象类型
接口:
/** * 根据学生编号获得学生对象 */ Student selectStudentById(int id);
映射:
<select id="selectStudentById" resultType="Student"> SELECT id,name,sex from student where id=#{id} select>
输出pojo对象list,方法返回值是List
接口:
/** * 根据学生姓名获得学生集合 */ ListselectStudentsByName(String name);
映射:
<select id="selectStudentsByName" parameterType="String" resultType="student"> SELECT id,name,sex from student where name like '%${value}%'; select>
生成的动态代理对象中是根据mapper.java方法的返回值类型确定是调用selectOne(返回单个对象调用)还是selectList(返回集合对象调用)
2.2.2、ResultMap
MyBatis中使用ResultMap完成自定义输出结果映射,如一对多,多对多关联关系。
问题:
假定POJO对象与表中的字段不一致,如下所示:
接口:
/** * 根据性别获得学生集合 */ ListselectStudentsBySex(String sex);
映射:
<select id="selectStudentsBySex" parameterType="String" resultType="stu"> SELECT id,name,sex from student where sex=#{sex}; select>
测试:
/** * Method: selectStudentsBySex(String sex) */ @Test public void testSelectStudentsBySex() throws Exception { Liststudents=dao.selectStudentsBySex("boy"); System.out.println(students); Assert.assertNotNull(students.get(0)); }
结果:
(一)、定义并引用ResultMap
修改映射文件:
<resultMap id="stuMap" type="stu">
<result column="id" property="stu_id">result>
<result column="name" property="stu_name">result>
<result column="sex" property="stu_sex">result>
resultMap>
<select id="selectStudentsBySex" parameterType="String" resultMap="stuMap">
SELECT id,name,sex from student where sex=#{sex};
select>
测试结果:
(二)、使用别名
修改映射文件:
<select id="selectStudentsBySex" parameterType="String" resultType="stu"> SELECT id stu_id,name stu_name,sex as stu_sex from student where sex=#{sex}; select>
测试结果:
(三)、注解上使用Result
@Results({ @Result(property ="id",column = "id"), @Result(property = "name",column = "name") }) @Select("select * from Category where id=#{id}") Category findById(int id);
2.2.3、返回Map
假定要返回id作为key,name作为value的Map。
接口:
/** * 获得所有学生Map集合 */ List
映射:
<resultMap id="stuKeyValueMap" type="HashMap"> <result property="name" column="NAME">result> <result property="value" column="VALUE">result> resultMap> <select id="selectAllStudents" resultMap="stuKeyValueMap"> SELECT id NAME,name VALUE from student; select>
测试:
/** * Method: selectAllStudents() */ @Test public void testSelectAllStudents() throws Exception { List
结果:
<resultMap id="pieMap" type="HashMap"> <result property="value" column="VALUE" /> <result property="name" column="NAME" /> resultMap> <select id="queryPieParam" parameterType="String" resultMap="pieMap"> SELECT PLAT_NAME NAME, <if test='_parameter == "总量"'> AMOUNT VALUE if> <if test='_parameter == "总额"'> TOTALS VALUE if> FROM DOMAIN_PLAT_DEAL_PIE ORDER BY <if test='_parameter == "总量"'> AMOUNT if> <if test='_parameter == "总额"'> TOTALS if> ASC select>
用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。
如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。
最终完成的映射器:
<?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.zhangguo.mybatis03.dao.StudentMapper"> <select id="selectStudentById" resultType="Student"> SELECT id,name,sex from student where id=#{id} select> <select id="selectStudentsCount" resultType="long"> SELECT count(*) from student select> <select id="selectStudentsByName" parameterType="String" resultType="student"> SELECT id,name,sex from student where name like '%${value}%'; select> <resultMap id="stuKeyValueMap" type="HashMap"> <result property="name" column="NAME">result> <result property="value" column="VALUE">result> resultMap> <select id="selectAllStudents" resultMap="stuKeyValueMap"> SELECT id NAME,name VALUE from student; select> <resultMap id="stuMap" type="stu"> <result column="id" property="stu_id">result> <result column="name" property="stu_name">result> <result column="sex" property="stu_sex">result> resultMap> <select id="selectStudentsBySex" parameterType="String" resultType="stu"> SELECT id stu_id,name stu_name,sex as stu_sex from student where sex=#{sex}; select> <select id="selectStudentsByNameOrSex" resultType="student"> SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex}; select> <select id="selectStudentsByIdOrSex" resultType="student"> SELECT id,name,sex from student where id=#{no} or sex=#{sex}; select> <insert id="insertStudent" parameterType="student"> insert into student(name,sex) VALUES(#{name},'${sex}') insert> <update id="updateStudent" parameterType="student"> update student set name=#{name},sex=#{sex} where id=#{id} update> <delete id="deleteStudent" parameterType="int"> delete from student where id=#{id} delete> <delete id="deleteStudents"> delete from student where id in <foreach collection="list" item="id" open="(" separator="," close=")"> #{id} foreach> delete> mapper>
最终完成的数据访问类似:
package com.zhangguo.mybatis03.dao; import com.zhangguo.mybatis03.entities.Stu; import com.zhangguo.mybatis03.entities.Student; import com.zhangguo.mybatis03.utils.SqlSessionFactoryUtil; import org.apache.ibatis.session.SqlSession; import java.util.List; import java.util.Map; public class StudentDao implements StudentMapper { /** * 根据学生编号获得学生对象 */ public Student selectStudentById(int id) { Student entity = null; //打开一个会话 SqlSession session = SqlSessionFactoryUtil.openSession(true); //获得一个映射器 StudentMapper mapper = session.getMapper(StudentMapper.class); //查询单个对象,指定参数为3 entity = mapper.selectStudentById(id); //关闭 SqlSessionFactoryUtil.closeSession(session); return entity; } /** * 获得学生总数 */ public long selectStudentsCount() { long count = 0; //打开一个会话 SqlSession session = SqlSessionFactoryUtil.openSession(true); //获得一个映射器 StudentMapper mapper = session.getMapper(StudentMapper.class); //查询单行单列,简单值 count = mapper.selectStudentsCount(); //关闭 SqlSessionFactoryUtil.closeSession(session); return count; } /** * 根据学生姓名获得学生集合 */ public ListselectStudentsByName(String name) { List entities = null; //打开一个会话 SqlSession session = SqlSessionFactoryUtil.openSession(true); //获得一个映射器 StudentMapper mapper = session.getMapper(StudentMapper.class); //查询多个对象,指定参数 entities = mapper.selectStudentsByName(name); //关闭 SqlSessionFactoryUtil.closeSession(session); return entities; } /** * 获得所有学生Map集合 * */ public List
最终完成的接口:
package com.zhangguo.mybatis03.dao; import com.zhangguo.mybatis03.entities.Stu; import com.zhangguo.mybatis03.entities.Student; import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Map; public interface StudentMapper { /** * 根据学生编号获得学生对象 */ Student selectStudentById(int id); /** * 获得学生总数 * */ long selectStudentsCount(); /** * 根据学生姓名获得学生集合 */ ListselectStudentsByName(String name); /** * 获得所有学生Map集合 */ List > selectAllStudents(); /** * 根据性别获得学生集合 */ List selectStudentsBySex(String sex); /** * 根据学生姓名或性别获得学生集合 */ List selectStudentsByNameOrSex(@Param("realname") String name,@Param("sex") String sex); /** * 根据学生Id或性别获得学生集合 */ List selectStudentsByIdOrSex(Map param); /** * 添加学生 */ int insertStudent(Student entity); /** * 更新学生 */ int updateStudent(Student entity); /** * 删除学生 */ int deleteStudent(int id); /** * 删除多个学生通过编号 */ int deleteStudents(List ids); }
最终完成的测试:
package com.zhangguo.mybatis03.dao; import com.zhangguo.mybatis03.entities.Stu; import com.zhangguo.mybatis03.entities.Student; import org.junit.*; import org.junit.runners.MethodSorters; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * StudentDao Tester. * * @author* @version 1.0 * @since09/26/2018*/ @FixMethodOrder(MethodSorters.JVM)//指定测试方法按定义的顺序执行 public class StudentDaoTest { StudentMapper dao; @Before public void before() throws Exception { dao=new StudentDao(); } @After public void after() throws Exception { } /** * Method: selectStudentById(int id) */ @Test public void testSelectStudentById() throws Exception { Student entity=dao.selectStudentById(1); System.out.println(entity); Assert.assertNotNull(entity); } // /** * Method: selectStudentsCount() */ @Test public void testSelectStudentsCount() throws Exception { Assert.assertNotEquals(0,dao.selectStudentsCount()); } /** * Method: selectStudentsByName(String name) */ @Test public void testSelectStudentsByName() throws Exception { Liststudents=dao.selectStudentsByName("C"); System.out.println(students); Assert.assertNotNull(students); } /** * Method: selectAllStudents() */ @Test public void testSelectAllStudents() throws Exception { List > students=dao.selectAllStudents(); System.out.println(students); Assert.assertNotNull(students); } /** * Method: selectStudentsBySex(String sex) */ @Test public void testSelectStudentsBySex() throws Exception { List students=dao.selectStudentsBySex("boy"); System.out.println(students); Assert.assertNotNull(students.get(0)); } /** * Method: selectStudentsByIdOrSex */ @Test public void testSelectStudentsByNameOrSex() throws Exception { Map param=new HashMap (); param.put("no",1); param.put("sex","girl"); List students=dao.selectStudentsByIdOrSex(param); System.out.println(students); Assert.assertNotNull(students); } /** * Method: insertStudent */ @Test public void testInsertStudent() throws Exception { Student entity=new Student(); //entity.setName("张明"); entity.setSex("boy"); Assert.assertEquals(1,dao.insertStudent(entity)); } /** * Method: updateStudent */ @Test public void testUpdateStudent() throws Exception { Student entity=dao.selectStudentById(11); //entity.setName("张丽美"); entity.setSex("girl"); Assert.assertEquals(1,dao.updateStudent(entity)); } /** * Method: deleteStudent */ @Test public void testDeleteStudent() throws Exception { Assert.assertEquals(1,dao.deleteStudent(12)); } /** * Method: deleteStudents */ @Test public void testDeleteStudents() throws Exception { List ids=new ArrayList (); ids.add(10); ids.add(11); Assert.assertEquals(2,dao.deleteStudents(ids)); } }
三、多表关联查询
表与表之间有三种常见的关联关系,分别是一对一,一对多与多对多关系,MyBatis直接提供一对一与一对多的关联关系,可能通过间接的方式实现一对多关联。
3.1、一对一关系
3.1.1、执行环境
假定一个员工(emp)拥有一个登录用户(user),员工与用户表之间是一对一关系:
用户表:
员工表:
SQL:
SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `user` -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `username` varchar(64) NOT NULL COMMENT '用户名', `password` varchar(64) NOT NULL COMMENT '密码', PRIMARY KEY (`id`), UNIQUE KEY `users_username_uindex` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='用户表'; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES ('1', 'tom', '123456'); INSERT INTO `user` VALUES ('2', 'rose', '888888'); INSERT INTO `user` VALUES ('3', 'mark', 'qwerty'); INSERT INTO `user` VALUES ('4', 'jack', 'qaz123'); INSERT INTO `user` VALUES ('5', 'mali', 'uio890'); SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `emp` -- ---------------------------- DROP TABLE IF EXISTS `emp`; CREATE TABLE `emp` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `user_id` int(11) DEFAULT NULL COMMENT '用户编号', `realname` varchar(32) NOT NULL COMMENT '姓名', `email` varchar(64) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`id`), KEY `emp_user_id` (`user_id`), CONSTRAINT `emp_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='员工表'; -- ---------------------------- -- Records of emp -- ---------------------------- INSERT INTO `emp` VALUES ('1', '1', '汤姆', 'tom@gmail.com'); INSERT INTO `emp` VALUES ('2', '2', '梅贵', 'rose@163.com'); INSERT INTO `emp` VALUES ('3', '3', '马克', 'mark@sina.com'); INSERT INTO `emp` VALUES ('4', '4', '岳翰', 'jack@gmail.com'); INSERT INTO `emp` VALUES ('5', '5', '马丽', 'mali@sina.com');
关系:
3.1.2、关联查询(1次查询)
实体:
用户:
package com.zhangguo.mybatis03.entities; /**用户POJO*/ public class User { private int id; private String username; private String password; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
员工:
package com.zhangguo.mybatis03.entities; /**员工POJO*/ public class Emp { private int id; /**用户编号*/ private int user_id; private String realname; private String email; /**用户对象*/ private User user; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getUser_id() { return user_id; } public void setUser_id(int user_id) { this.user_id = user_id; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public User getUser() { return user; } public Emp setUser(User user) { this.user = user; return this; } }
接口:
package com.zhangguo.mybatis03.dao; import com.zhangguo.mybatis03.entities.Emp; /**员工数据访口*/ public interface EmpMapper { /**获得员工通过员工编号*/ Emp getEmpById_1(int id); }
映射:
<?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.zhangguo.mybatis03.dao.EmpMapper"> <select id="getEmpById_1" resultMap="empMap_1" parameterType="int"> SELECT emp.id, emp.user_id, emp.realname, emp.email, `user`.username, `user`.`password` FROM emp INNER JOIN `user` ON emp.user_id = `user`.id where emp.id=#{id} select> <resultMap id="empMap_1" type="Emp"> <id property="id" column="id">id> <result property="user_id" column="user_id">result> <result property="realname" column="realname">result> <result property="email" column="email">result> <association property="user" javaType="User"> <id property="id" column="user_id">id> <result property="username" column="username">result> <result property="password" column="password">result> association> resultMap> mapper>
测试:
package com.zhangguo.mybatis03.dao; import com.zhangguo.mybatis03.entities.Emp; import org.junit.Assert; import org.junit.Test; import org.junit.Before; import org.junit.After; /** * EmpDao Tester. * * @author* @version 1.0 * @since09/30/2018*/ public class EmpDaoTest { EmpMapper empDao; @Before public void before() throws Exception { empDao=new EmpDao(); } @After public void after() throws Exception { } /** * Method: getEmpById_1(int id) * 获得员工通过员工编号 */ @Test public void testGetEmpById_1() throws Exception { Emp entity=empDao.getEmpById_1(1); System.out.println(entity); Assert.assertNotNull(entity); } }
结果:
3.1.3、嵌套查询(2次查询)
实体:同上
接口:
/**获得员工通过员工编号,多次查询*/ Emp getEmpById_2(int id);
映射:
<select id="getEmpById_2" resultMap="empMap_2"> SELECT emp.id, emp.user_id, emp.realname, emp.email FROM emp where id=#{id} select> <resultMap id="empMap_2" type="Emp"> <id property="id" column="id">id> <result property="user_id" column="user_id">result> <result property="realname" column="realname">result> <result property="email" column="email">result> <association property="user" column="user_id" select="selectUserById">association> resultMap> <select id="selectUserById" resultType="User"> SELECT `user`.id, `user`.username, `user`.`password` FROM `user` where id=#{id} select>
测试:
/** * Method: getEmpById_2(int id) * 获得员工通过员工编号,一对一方法二 */ @Test public void testGetEmpById_2() throws Exception { Emp entity=empDao.getEmpById_2(2); System.out.println(entity); Assert.assertNotNull(entity); }
结果:
MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:
- property:对象属性的名称
- javaType:对象属性的类型
- column:所对应的外键字段名称
- select:使用另一个查询封装的结果
3.2、一对多关系
3.2.1、执行环境
一个用户帐号可以被多个员工使用,形成一个一对多的关系,表中的数据如下:
员工表emp:
用户表user:
3.2.2、关联查询(1次查询)
实体:
员工:
package com.zhangguo.mybatis03.entities; /**员工POJO*/ public class Emp { private int id; /**用户编号*/ private int user_id; private String realname; private String email; /**用户对象*/ private User user; public int getId() { return id; } public void setId(int id) { this.id = id; } public int getUser_id() { return user_id; } public void setUser_id(int user_id) { this.user_id = user_id; } public String getRealname() { return realname; } public void setRealname(String realname) { this.realname = realname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public User getUser() { return user; } public Emp setUser(User user) { this.user = user; return this; } @Override public String toString() { return "Emp{" + "id=" + id + ", user_id=" + user_id + ", realname='" + realname + '\'' + ", email='" + email + '\'' + ", user=" + user + '}'; } }
用户:
package com.zhangguo.mybatis03.entities; import java.util.List; /**用户POJO*/ public class User { private int id; private String username; private String password; /**员工集合,一个用户对象对应多个员工对象*/ private Listemps; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public ListgetEmps() { return emps; } public User setEmps(List emps) { this.emps = emps; return this; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", emps=" + emps + '}'; } }
接口:
/**获得用户通过用户编号,1对多级联查询*/ User getUserById_1(int id);
映射:
<select id="getUserById_1" resultMap="userMap_1" parameterType="int"> SELECT emp.id, emp.user_id, emp.realname, emp.email, `user`.username, `user`.`password` FROM emp INNER JOIN `user` ON emp.user_id = `user`.id where `user`.id=#{id} select> <resultMap id="userMap_1" type="User"> <id property="id" column="user_id">id> <result property="username" column="username">result> <result property="password" column="password">result> <collection property="emps" ofType="Emp"> <id property="id" column="id">id> <result property="user_id" column="user_id">result> <result property="realname" column="realname">result> <result property="email" column="email">result> collection> resultMap>
测试:
/** * Method: getUserById_1(int id) * 获得用户过用户编号,级联查询 */ @Test public void testGetUserById_1() throws Exception { User entity=empDao.getUserById_1(2); System.out.println(entity); Assert.assertNotNull(entity); }
结果:
上面的示例中会发现User对象中包含多个Emp对象,此时的Emp对象中又引用了User对象,但值是空的,如果想设置值可以继续用1对1的办法赋值:
映射:
<resultMap id="userMap_1" type="User"> <id property="id" column="user_id">id> <result property="username" column="username">result> <result property="password" column="password">result> <collection property="emps" ofType="Emp"> <id property="id" column="id">id> <result property="user_id" column="user_id">result> <result property="realname" column="realname">result> <result property="email" column="email">result> <association property="user" javaType="User"> <id property="id" column="user_id">id> <result property="username" column="username">result> <result property="password" column="password">result> association> collection> resultMap>
结果:
3.1.3、嵌套查询(多次查询)
实体:同上
接口:
/**获得用户通过用户编号,1对多嵌套查询*/ User getUserById_2(int id);
映射:
<select id="getUserById_2" resultMap="userMap_2" parameterType="int"> SELECT `user`.id, `user`.username, `user`.`password` FROM `user` where id=#{id} select> <resultMap id="userMap_2" type="User"> <id property="id" column="id">id> <result property="username" column="username">result> <result property="password" column="password">result> <collection property="emps" ofType="Emp" column="id" select="selectEmpById">collection> resultMap> <select id="selectEmpById" resultType="Emp"> SELECT emp.id, emp.user_id, emp.realname, emp.email FROM emp where user_id=#{id} select>
测试:
/** * Method: getUserById_2(int id) * 获得用户过用户编号,嵌套查询 */ @Test public void testGetUserById_2() throws Exception { User entity=empDao.getUserById_2(5); System.out.println(entity); Assert.assertNotNull(entity); }
结果:
MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。
四、动态SQL
4.0、MySQL环境与前置要求
数据与SQL环境如下:
前置要求:
4.1、什么是动态SQL
MyBatis的动态SQL是基于OGNL的表达式的。它对SQL语句进行灵活的操作,通过表达式判断来实现对SQL的灵活拼接、组装。
mybatis核心对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。
主要通过以下标签:if,where,choose(when,otherwise),trim,set,foreach。
4.2、if条件判断
根据 name和 sex 来查询数据。如果name为空,那么将只根据sex来查询;反之只根据name来查询
首先不使用 动态SQL 来书写
接口:
/** * 根据学生姓名和性别获得学生集合 */ ListselectStudentsByNameAndSex(@Param("name") String name,@Param("sex") String sex);
映射:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student where name=#{name} and sex=#{sex}; select>
测试:
/** * Method: selectStudentsByNameAndSex */ @Test public void testSelectStudentsByNameAndSex() throws Exception { Liststudents=dao.selectStudentsByNameAndSex("rose",null); System.out.println(students); Assert.assertNotNull(students); }
结果:
上面的查询语句,我们发现如果 #{sex} 为空,那么查询结果也是空,如何解决这个问题呢?使用 if 来判断
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student where 1=1 <if test="name!=null and name!=''"> and name=#{name} if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> select>
结果:
参考:
<select id="getStudentList_if" resultMap="resultMap_studentEntity" parameterType="liming.student.manager.data.model.StudentEntity"> SELECT ST.STUDENT_ID, ST.STUDENT_NAME, ST.STUDENT_SEX, ST.STUDENT_BIRTHDAY, ST.STUDENT_PHOTO, ST.CLASS_ID, ST.PLACE_ID FROM STUDENT_TBL ST WHERE <if test="studentName !=null "> ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%') if> <if test="studentSex != null and studentSex != '' "> AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER} if> <if test="studentBirthday != null "> AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE} if> <if test="classId != null and classId!= '' "> AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR} if> <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' "> AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR} if> <if test="placeId != null and placeId != '' "> AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR} if> <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' "> AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR} if> <if test="studentId != null and studentId != '' "> AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR} if> select>
虽然1=1这种方法结合if可以解决我们的需求,但是1=1明显是冗余的,通过where可以解决。
4.3、where条件
where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。
修改后的映射:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student <where> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%'); if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> where> select>
测试:
/** * Method: selectStudentsByNameAndSex */ @Test public void testSelectStudentsByNameAndSex() throws Exception { Liststudents=dao.selectStudentsByNameAndSex("a",null); System.out.println(students); Assert.assertNotNull(students); }
结果:
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
where标记的作用类似于动态sql中的set标记,他的作用主要是用来简化sql语句中where条件判断的书写的,如下所示:
<select id="selectByParams" parameterType="map" resultType="user"> select * from user <where> <if test="id != null ">id=#{id}if> <if test="name != null and name.length()>0" >and name=#{name}if> <if test="gender != null and gender.length()>0">and gender = #{gender}if> where> select>
在上述SQL中加入ID的值为null的话,那么打印出来的SQL为:select * from user where name="xx" and gender="xx"
where 标记会自动将其后第一个条件的and或者是or给忽略掉
4.4、if+set设置值
当update语句中没有使用if标签时,如果有一个参数为null,都会导致错误。
当在update语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置SET 关键字,和剔除追加到条件末尾的任何不相关的逗号。如果set包含的内容为空的话则会出错。
使用if+set标签修改后,如果某项为null则不进行更新,而是保持数据库原值。
如果通过if判断表面可以解决问题,如下所示:
<update id="updateStudent" parameterType="student"> update student set <if test="name!=null and name.lenght()>0"> name=#{name} , if> <if test="sex!=null and sex.lenght()>0"> sex=#{sex} if> where id=#{id} update>
这样做也会有问题,就是当sex为空时的sql就变成了 update student set name=#{name} , where id=#{id},这明显是错误的。
同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?
接口:
/** * 更新学生 */ int updateStudent(Student entity);
映射:
<update id="updateStudent" parameterType="student"> update student <set> <if test="name!=null and name.length()>0"> name=#{name} if> <if test="sex!=null and sex.length()>0"> sex=#{sex} if> set> where id=#{id} update>
注意:某些情况下逗号必须添加,如下所示:
<update id="updateStudent" parameterType="student"> update student <set> <if test="name!=null and name.length()>0"> name=#{name} , if> <if test="sex!=null and sex.length()>0"> sex=#{sex} , if> set> where id=#{id} update>
结尾的逗号会被自动删除。
测试:
/** * Method: updateStudent */ @Test public void testUpdateStudent() throws Exception { //会将实体中的每一个字段都更新,不好 // Student entity=dao.selectStudentById(11); // //entity.setName("张丽美"); // entity.setSex("girl"); // // Assert.assertEquals(1,dao.updateStudent(entity)); //不需要先执行查询 Student student=new Student(); student.setId(9); //只更新了name与sex没有关系 student.setName("malili"); Assert.assertEquals(1,dao.updateStudent(student)); }
结果:
这样写,如果第一个条件 name 为空,那么 sql 语句为:update student set sex=? where id=?
如果第一个条件不为空,那么 sql 语句为:update student u set name= ? , sex = ? where id=?
set主要解决了自动添加标签与处理逗号的问题,另外这种更新方法比较以前的全部更新方式在开发中性能更高。
4.5、choose(when,otherwise) 开关
如果不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。
假定这里需要优先根据编号搜索,没有时选择name,最后考虑sex:
接口:
/** * 根据学生编号、姓名和性别获得学生集合 */ ListselectStudentsByNameAndSex(@Param("id") int id, @Param("name") String name,@Param("sex") String sex);
映射:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student <where> <choose> <when test="id>0"> id=#{id} when> <when test="name!=null and name!=''"> name=#{name} when> <otherwise> sex=#{sex} otherwise> choose> where> select>
测试:
/** * Method: selectStudentsByNameAndSex */ @Test public void testSelectStudentsByNameAndSex() throws Exception { Liststudents=dao.selectStudentsByNameAndSex(1,"rose","girl"); System.out.println(students); Assert.assertNotNull(students); }
结果:
也就是说,这里我们有三个条件,id,name,sex,只能选择一个作为查询条件
如果 id 不为空,那么查询语句为:select * from student where id=?
如果 id 为空,那么看name是否为空,如果不为空,那么语句为 select * from student where name=?;
如果name为空,那么查询语句为 select * from student where sex=?
4.6、trim裁剪
trim标记是一个格式化的标记,可以完成set或者是where标记的功能
①、用 trim 改写上面第二点的 if+where 语句
if+where的办法:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student <where> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%'); if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> where> select>
trim的办法:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student <trim prefix="where" prefixOverrides="and | or"> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%') if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> trim> select>
测试结果:
prefix:将加上前缀
prefixoverride:去掉第一个and或者是or
②、用 trim 改写上面第三点的 if+set 语句
if+set的方法:
<update id="updateStudent" parameterType="student"> update student <set> <if test="name!=null and name.length()>0"> name=#{name} if> <if test="sex!=null and sex.length()>0"> sex=#{sex} if> set> where id=#{id} update>
trim的方法:
<update id="updateStudent" parameterType="student"> update student <trim prefix="set" suffixOverrides=","> <if test="name!=null and name.length()>0"> name=#{name}, if> <if test="sex!=null and sex.length()>0"> sex=#{sex}, if> trim> where id=#{id} update>
结果:
suffix:后缀
suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)
可以自定义添加前后缀,与之对应的属性是prefix和suffix。同时通过prefixOverrides和suffixOverrides分别来覆盖首尾部的内容,即忽略不必要的前后缀。就是说它可以充当where标签,也可以充当set标签啦~
充当where标签:
<trim prefix = "where" prefixOverrides="and|or" > ... trim>
充当set标签:
<trim prefix="set" suffixOverrides=","> ... trim>
例子:动态添加用户属性
<insert id="find" resultType="Admin"> insert into admin <trim prefix="(" suffix=")" suffixOverrides=","> <if test = "aname != null and aname !='' "> aname, if> <if test = "city != null and city !='' "> city, if> <if test = "age != null and age !='' "> age, if> trim> <trim prefix="values(" suffix=")" suffixOverrides=","> <if test = "aname != null and aname !='' "> #{aname}, if> <if test = "city != null and city !='' "> #{city}, if> <if test = "age != null and age !='' "> #{age}, if> trim> insert>
上面相应的语句为:insert into admin (…) values(…);。通过trim标签用()包裹,以及自动忽略尾部的逗号。
4.7、SQL 片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
比如:下面的映射文件中对于id,name,sex出现多次:
<select id="selectStudentsByNameOrSex" resultType="student"> SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex}; select> <select id="selectStudentsByIdOrSex" resultType="student"> SELECT id,name,sex from student where id=#{no} or sex=#{sex}; select> <select id="selectStudentsByNameAndSex" resultType="student"> SELECT id,name,sex from student <trim prefix="where" prefixOverrides="and | or"> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%') if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> trim> select>
加粗的内容是重复的,通过sql片段复用。
定义sql片段:
<sql id="col_student">
id,name,sex
sql>
引用 sql 片段
<select id="selectStudentsByNameOrSex" resultType="student"> SELECT <include refid="col_student">include> from student where name like '%${realname}%' or sex=#{sex}; select> <select id="selectStudentsByIdOrSex" resultType="student"> SELECT <include refid="col_student">include> from student where id=#{no} or sex=#{sex}; select> <select id="selectStudentsByNameAndSex" resultType="student"> SELECT <include refid="col_student">include> from student <trim prefix="where" prefixOverrides="and | or"> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%') if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> trim> select>
结果:
注意:①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
sql片段带参数:
定义时使用参数:
<sql id="col_student">
${alias}.id,${alias}.name,${alias}.sex
sql>
引用时指定参数:
<select id="selectStudentsByNameAndSex" resultType="student"> SELECT <include refid="col_student"> <property name="alias" value="s">property> include> from student s <trim prefix="where" prefixOverrides="and | or"> <if test="name!=null and name!=''"> and name like concat(concat('%',#{name}),'%') if> <if test="sex!=null and sex!=''"> and sex=#{sex} if> trim> select>
结果:
4.8、foreach循环
foreach元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及在迭代结果之间放置分隔符。这个元素是很智能的,因此它不会偶然地附加多余的分隔符。
注意 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象传递给 foreach 作为集合参数。当使用可迭代对象或者数组时,index 是当前迭代的次数,item 的值是本次迭代获取的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
到此我们已经完成了涉及 XML 配置文件和 XML 映射文件的讨论。下一章将详细探讨 Java API,这样就能提高已创建的映射文件的利用效率。
foreach的主要用在构建in条件中,他可以迭代一个集合。foreach元素的属性主要有:item,index,collection,open,separator,close。
下面对属性进行简单的介绍:
item:表示集合中每一个元素进行迭代时的别名。
index:指定一个名字,用于表示在迭代过程中每次迭代的位置。
open:表示以什么开始。
separator:每次迭代以什么分割。
close:以什么关闭。
collection:最重要且必须指定的有三种情况:
1.如果传入的是单独参数的List类型时,collection的属性值为list。
2.如果传入的是单独参数的数组时,collection的属性值为array。
3.如果传入多个参数时,我们把多个参数放入map中,单参数也可以放入map中。map中的key就是参数名,所以collection属性值就是传入的List或者array对象在Map里的key。
一、用 foreach 来改写 select * from user where id=1 or id=2 or id=3
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User"> select * from user <where> <foreach collection="ids" item="id" open="and (" close=")" separator="or"> id=#{id} foreach> where> select>
二、我们用 foreach 来改写 select * from user where id in (1,2,3)
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User"> select * from user <where> <foreach collection="ids" item="id" open="and id in (" close=") " separator=","> #{id} foreach> where> select>
其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改。
参考:
<?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="cn.bdqn.dao.UserMapper"> <resultMap type="User" id="seachUserResult"> <result property="id" column="id"/> <result property="userCode" column="userCode" /> <result property="userName" column="userName" /> <result property="roleId" column="roleId" /> <association property="role" javaType="Role" resultMap="roleMap"/> resultMap> <resultMap type="Role" id="roleMap"> <result property="id" column="id"/> <result property="roleCode" column="roleCode"/> <result property="roleName" column="roleName"/> resultMap> <select id="getUserListByRoleId" parameterType="Role" resultMap="seachUserResult"> select u.*,r.roleCode as roleCode,r.roleName as roleName from user u,role r where u.roleId = r.id and u.roleId = #{id} select> <resultMap type="User" id="userMap"> <id property="id" column="userId"/> <collection property="addressList" ofType="Address"> <id property="id" column="a_id"/> <result property="postCode" column="postCode"/> <result property="addressContent" column="addressContent"/> collection> resultMap> <select id="getAddressListByUserId" parameterType="User" resultMap="userMap"> select *,a.id as a_id from user u,address a where u.id=a.userId and u.id=#{id} select> <resultMap type="User" id="seachUser"> <result property="id" column="id"/> <result property="userCode" column="userCode"/> <result property="userName" column="userName"/> <result property="roleId" column="roleId"/> <result property="roleName" column="roleName"/> resultMap> <select id="searchUserList" parameterType="User" resultMap="seachUser"> select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id <if test="roleId!=null"> and u.roleId = #{roleId} if> <if test="userCode != null"> and u.userCode like CONCAT ('%',#{userCode},'%') if> <if test="userName != null"> and u.userName like CONCAT ('%',#{userName},'%') if> select> <select id="count" resultType="int"> select count(1) from user select> <insert id="add" parameterType="User"> insert into user (userCode,userName,userPassword) values (#{userCode},#{userName},#{userPassword}) insert> <update id="update" parameterType="User"> update user <trim prefix="set" suffixOverrides=","> <if test="userCode != null and userCode != ''">userCode=#{userCode},if> <if test="userName != null">userName=#{userName},if> <if test="userPassword != null">userPassword=#{userPassword},if> <if test="roleId != null">roleId=#{roleId}if> trim> where id=#{id} update> <resultMap type="User" id="userMapByDep"> <result property="id" column="id"/> <result property="userCode" column="userCode"/> <result property="userName" column="userName"/> resultMap> <select id="getUserByDepId_foreach_array" resultMap="userMapByDep"> select * from user where depId in <foreach collection="array" item="depIds" open="(" separator="," close=")"> #{depIds} foreach> select> <select id="getUserByDepId_foreach_list" resultMap="userMapByDep"> select * from user where depId in <foreach collection="list" item="depIdList" open="(" separator="," close=")"> #{depIdList} foreach> select> <delete id="delete" parameterType="User"> delete from user where id=#{id} delete> <select id="getUserList" resultType="User"> select * from user select> mapper>
假定我们要多删除:
接口:
/** * 删除多个学生通过编号 */ int deleteStudents(Listids);
映射:
<delete id="deleteStudents"> delete from student where id in <foreach collection="list" item="id" open="(" separator="," close=")"> #{id} foreach> delete>
注意collection这里只能是list,不能是ids,因为反射时获取不到参数名称。
测试:
/** * Method: deleteStudents */ @Test public void testDeleteStudents() throws Exception { Listids=new ArrayList (); ids.add(8); ids.add(9); Assert.assertEquals(2,dao.deleteStudents(ids)); }
结果:
4.9、bind 绑定变量
bind标签可以使用OGNL表达式创建一个变量并将其绑定到上下文中。
bind标签的两个属性都是不选项,name为绑定到上下文的变量名,value为OGNL表达式,创建一个bind标签后,就可以在下面直接使用了。 使用bind拼接字符串不仅可以避免因更换数据库而修改SQL,也能预防SQL注入。
<select id="getEmpsTestInnerParameter" resultType="com.hand.mybatis.bean.Employee">
<bind name="bindeName" value="'%'+eName+'%'"/> eName是employee中一个属性值
SELECT * FROM emp
<if test="_parameter!=null">
where ename like #{bindeName}
if>
select>
五、视频
https://www.bilibili.com/video/av32447485/
六、示例
https://git.coding.net/zhangguo5/MyBatis02.git
https://git.coding.net/zhangguo5/MyBatis03.git
七、作业
0、完成一个多删除功能,前端可以勾选任意条记录,后端实现多删除功能,删除前提示是否删除。
1、个人项目的数据库设计,个人项目的静态页面设计(2个,一个必须是首页,PC端)
2、重现本章示例
3、任务指导手册所有mybatis理论题
4、根据如下ER图创建4个表,完成1-1,1-N,M-N关系的查询,无需界面,测试通过即可
5、完成图书管理系统中二表关联,显示图书类型
请实现一个简易图书管理系统(LibSystem),实现图书管理功能,要求如下: 1、管理数据库中所有图书(Books),包含图书编号(isbn)、书名(title)、作者(author)、价格(price)、出版日期(publishDate) 2、Maven多模块+MySQL+Git+MyBatis+JUnit单元测试 3、表示层可以是AJAX或JSTL 请实现一个迷你图书管理系统(LibSystem),实现图书管理功能,要求如下: 1、管理数据库中所有图书分类(Categories),包含图书编号(id),名称(name) 2、管理数据库中所有图书(Books),包含图书编号(isbn)、类别(categoryId,外键)书名(title)、作者(author)、价格(price)、出版日期(publishDate)、封面(cover)、详细介绍(details) 3、分页 4、多条件组件查询(3个以上的条件) 5、上传封面 6、富文本编辑器
6、使用任意的原型开发工具设计出个人项目的1-2个界面,工具:Balsamiq Mockups,Axure RP Pro 7.0。
7、将动态sql中的所有知识点全部应用于作业4
1、重现上所有上课示例
2、请使用Maven多模块+Git+MyBatis完成一个单表的管理功能,需要UI,可以AJAX也可以JSTL作表示层。
3、分页,多条件组合查询,多表连接(选作)
4、内部测试题(4个小时)
4.1、请实现一个简易图书管理系统(LibSystem),实现图书管理功能,要求如下:(初级)
1、管理数据库中所有图书(Books),包含图书编号(isbn)、书名(title)、作者(author)、价格(price)、出版日期(publishDate)
2、Maven多模块+MySQL+Git+MyBatis+JUnit单元测试
3、表示层可以是AJAX或JSTL
C10 R(10+10) U10 D10
4.2、请实现一个迷你图书管理系统(LibSystem),实现图书管理功能,要求如下:(中级)
1、管理数据库中所有图书分类(Categories),包含图书编号(id),名称(name)
2、管理数据库中所有图书(Books),包含图书编号(isbn)、类别(categoryId,外键)书名(title)、作者(author)、价格(price)、出版日期(publishDate)、封面(cover)、详细介绍(details)
3、分页 10
4、多条件组件查询(3个以上的条件任意组合)(高级) 10
5、多删除 (高级) 10
6、上传封面 (高级) 10
7、富文本编辑器 (高级) 10