3.6 动态SQL dynamic-sql
有时候,静态的SQL语句并不能满足应用程序的需求。我们可以根据一些条件,来动态地构建 SQL语句。
例如,在Web应用程序中,有可能有一些搜索界面,需要输入一个或多个选项,然后根据这些已选择的条件去执行检索操作。在实现这种类型的搜索功能,我们可能需要根据这些条件来构建动态的SQL语句。如果用户提供了任何输入条件,我们需要将那个条件添加到SQL语句的WHERE子句中。MyBatis通过使用<if>,<choose>,<where>,<foreach>,<trim>元素提供了对构造动态SQL语句的高级别支持。
DynamicSql
3.6.1 If 条件
<if>元素被用来有条件地嵌入SQL片段,如果测试条件被赋值为true,则相应地SQL片段将会被添加到SQL语句中。假定我们有一个课程搜索界面,设置了讲师(Tutor)下拉列表框,课程名称(CourseName)文本输入框,开始时间(StartDate)输入框,结束时间(EndDate)输入框,作为搜索条件。假定课讲师下拉列表是必须选的,其他的都是可选的。
当用户点击搜索按钮时,我们需要显示符合以下条件的成列表:
特定讲师的课程
课程名
包含输入的课程名称关键字的课程;如果课程名称输入为空,则取所有课程
在开始时间和结束时间段内的课程
我们可以对应的映射语句,如下所示:
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
WHERE TUTOR_ID= #{tutorId}
<if test="courseName != null">
AND NAME LIKE #{courseName}
</if>
<if test="startDate != null">
AND START_DATE >= #{startDate}
</if>
<if test="endDate != null">
AND END_DATE <![CDATA[ <= ]]> #{endDate}
</if>
</select>
public interface DynamicSqlMapper{
List<Course> searchCourses(Map<String, Object> map);
}
public void searchCourses(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("tutorId", 1);
map.put("courseName", "%Java%");
map.put("startDate", new Date());
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses = mapper.searchCourses(map);
for (Course course : courses){
System.out.println(course);
}
}
此处将生成查询语句SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like ? AND START_DATE >= ?。
3.6.2 choose,when 和 otherwise 条件
有时候,查询功能是以查询类别为基础的。首先,用户需要先选择是否希望通过选择讲师,课程名称,开始时间,或结束时间作为查询条件类别来进行查询,然后根据选择的查询类别,输入相应的参数。在这样的情景中,我们【需要只使用其中一种】查询类别。
MyBatis提供了<choose>元素支持此类型的SQL预处理。
如果没有选择查询类别,则查询开始时间在今天之后的课程,代码如下:
注意:mysql中now()是当前时间 oracle需要使用sysdate
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<choose>
<when test="searchBy == 'Tutor'">
WHERE TUTOR_ID= #{tutorId}
</when>
<when test="searchBy == 'CourseName'">
WHERE name like #{courseName}
</when>
<otherwise>
WHERE start_date >= sysdate
</otherwise>
</choose>
</select>
MyBatis计算<choose>测试条件的值,且使用第一个值为TRUE的子句。如果没有条件为 true,则使用<otherwise>内的子句。
相当于我们常用的java代码中的这个例子:
if(){
..
}
else if(){
..
}
else{
..
}
测试例子:public void test_when(){
try {
SqlSession sqlSession=
MyBatisSqlSessionFactory.openSession();
Map<String,Object> map = new HashMap<String,Object>();
map.put("tutorId", 1);
map.put("courseName", "%Java%");
//输入根据什么类型来查询
map.put("searchBy", "Tutor");
//map.put("searchBy", "CourseName");
//map.put("startDate", new Date());
DynamicSqlMapper mapper =
sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses =
mapper.searchCourses(map);
for (Course course : courses){
System.out.println(course);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3.6.3 Where 条件
有时候,所有的查询条件应该是可选的。在需要使用至少一种查询条件的情况下,我们应该使用WHERE子句。并且如果有多个条件,我们需要在条件中添加AND或OR。
MyBatis提供了<where>元素支持这种类型的动态SQL语句。
在我们查询课程界面,我们假设所有的查询条件是可选的。然而,当需要提供一个或多个查询条件时,应该改使用WHERE子句。
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<where>
<if test="tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
<if test="startDate != null">
AND start_date >= #{startDate}
</if>
<if test="endDate != null">
AND end_date <![CDATA[ <= ]]> #{endDate}
</if>
</where>
</select>
<where>元素只有在其内部标签有返回内容时才会在动态语句上插入WHERE条件语句。并且,如果WHERE子句以AND或者OR打头,则打头的AND或OR将会被移除。例如:如果参数tutorId的值为null,并且courseName参数值不为null,则<where>标签会将AND name like #{courseName}中的AND移除掉,生成的SQL WHERE子句为:
where name like #{courseName}
3.6.4 <trim>条件
<trim>元素和<where>元素类似,但是<trim>提供了在添加前缀/后缀或者移除前缀/后缀方面提供更大的灵活性。
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<trim prefix="WHERE" suffixOverrides="or | and">
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId} and
</if>
<if test="courseName != null">
name like #{courseName} and
</if>
</trim>
</select>
prefix表示有一个if成立则插入where语句
suffix表示后缀,和prefix相反
suffixOverrides="and"表示如果最后生成的sql语句多一个and,则自动去掉.
prefixOverrides的意思是处理前缀,和suffixOverrides相反
这里如果任意一个<if>条件为true,<trim>元素会插入WHERE,并且移除紧跟WHERE后面的AND
3.6.5 foreach 循环
另外一个强大的动态SQL语句构造标签即是<foreach>。它可以迭代遍历一个数组或者列表,构造AND/OR条件或一个IN子句。
假设我们想找到tutor_id为 1,3,6的讲师所教授的课程,我们可以传递一个tutor_id组成的列表给映射语句,然后通过<foreach>遍历此列表构造动态SQL。
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
<foreach item="tutorId" collection="tutorIds">
OR tutor_id=#{tutorId}
</foreach>
</where>
</if>
</select>
代码:
public interface DynamicSqlMapper{
List<Course> searchCoursesByTutors(Map<String,Object> map);
}
public void searchCoursesByTutors(){
Map<String,Object> map = new HashMap<String,Object>();
List<Integer> tutorIds = new ArrayList<Integer>();
tutorIds.add(1);
tutorIds.add(3);
tutorIds.add(6);
map.put("tutorIds", tutorIds);
DynamicSqlMapper mapper =
sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses = mapper.searchCoursesByTutors(map);
for (Course course : courses){
System.out.println(course);
}
}
怎样使用<foreach>生成IN子句:
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
tutor_id IN
<foreach item="tempValue" collection="tutorIds" open="(" separator="," close=")">
#{tempValue}
</foreach>
</where>
</if>
</select>
3.6.6 set 条件
<set>元素和<where>元素类似,如果其内部条件判断有任何内容返回时,他会插入SET SQL 片段。
<update id="updateCourse" parameterType="Course">
update courses
<set>
<if test="name != null">name=#{name},</if>
<if test="description != null">description=#{description},</if>
<if test="startDate != null">start_date=#{startDate},</if>
<if test="endDate != null">end_date=#{endDate},</if>
</set>
where course_id=#{courseId}
</update>
这里,如果<if>条件返回了任何文本内容,<set>将会插入set关键字和其文本内容,并且会剔除将末尾的“,”。在上述的例子中,如果phone!=null,<set>将会让会移除phone=#{phone}后的逗号“,”,生成set phone=#{phone}
四 mybatis的一些特殊功能 special
除了简化数据库编程外,MyBatis还提供了各种功能,这些对实现一些常用任务非常有用,比如按页加载表数据,存取CLOB/BLOB类型的数据,处理枚举类型值,等等。
4.1 处理枚举类型
MyBatis支持持久化enum类型属性。假设t_user表中有一列gender(性别)类型为 varchar2(10),存储 MALE 或者 FEMALE 两种值。并且,Student对象有一个enum类型的gender 属性,如下所示:
public enum Gender {
FEMALE,MALE
}
默认情况下MyBatis使用EnumTypeHandler来处理enum类型的Java属性,并且将其存储为 enum值的名称。你不需要为此做任何额外的配置。你可以可以向使用基本数据类型属性一样使用enum类型属性,如下:
drop table t_user;
create table t_user(
id number primary key,
name varchar2(50),
gender varchar2(10)
);
public class User{
private Integer id;
private String name;
private Gender gender;
//setters and getters
}
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select my_seq.nextval from dual
</selectKey>
insert into t_user(id,name,gender)
values(#{id},#{name},#{gender})
</insert>
当你执行insertStudent语句的时候MyBatis会取Gender枚举(FEMALE/MALE)的名称,然后将其存储到GENDER列中。如果你希望存储原enum的顺序位置(0/1),而不是enum名,你需要明确地配置它
如果你想存储FEMALE为0,MALE为1到gender列中,你需要在mybatis-config.xml文件中配置EnumOrdinalTypeHandler:
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.briup.special.Gender"/>
注意:使用顺序位置为值存储到数据库时要当心。顺序值是根据enum中的声明顺序赋值的。如果你改变了Gender里面对象的声明顺序,则数据库存储的数据和此顺序值就不匹配了。练习:查询出来看看枚举是什么样的?
4.2 处理CLOB/BLOB类型数据
BLOB和CLOB都是大字段类型,BLOB是按二进制来存储的,而CLOB是可以直接存储文字的。通常像图片、文件、音乐等信息就用BLOB字段来存储,先将文件转为二进制再存储进去。而像文章或者是较长的文字,就用CLOB存储.
BLOB和CLOB在不同的数据库中对应的类型也不一样:
MySQL 中:clob对应text/longtext,blob对应blob
Oracle中:clob对应clob,blob对应blob
MyBatis提供了内建的对CLOB/BLOB类型列的映射处理支持。
drop table user_pics;
create table user_pics(
id number primary key,
name varchar2(50) ,
pic blob,
bio clob
);
这里,照片可以是PNG,JPG或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,MyBatis将CLOB类型的列映射到java.lang.String类型上、而把BLOB列映射到byte[]类型上。
public class UserPic{
private int id;
private String name;
private byte[] pic;
private String bio;
//setters & getters
}
<insert id="insertUserPic" parameterType="UserPic">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select my_seq.nextval from dual
</selectKey>
insert into user_pics(id,name, pic,bio)
values(#{id},#{name},#{pic},#{bio})
</insert>
<select id="getUserPicById" parameterType="int" resultType="UserPic">
select * from user_pics where id=#{id}
</select>
java代码:
@Test
public void test_insertUserPic(){
byte[] pic = null;
try {
//读取用户头像图片
File file = new File("src/com/briup/special/test.png");
InputStream is = new FileInputStream(file);
pic = new byte[is.available()];
is.read(pic);
is.close();
} catch (Exception e){
e.printStackTrace();
}
String name = "tom";
String bio = "可以是很长的字符串";
//准备好要插入到数据库中的数据并封装成对象
UserPic userPic = new UserPic(name, pic , bio);
SqlSession session = null;
try{
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
mapper.insertUserPic(userPic);
session.commit();
}catch (Exception e) {
e.printStackTrace();
session.rollback();
}finally {
if(session!=null)session.close();
}
}
下面的getUserPic()方法展示了怎样将CLOB类型数据读取到String类型,BLOB类型数据读取成byte[]属性:
@Test
public void test_getUserPicById(){
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
UserPic userPic = mapper.getUserPicById(24);
System.out.println(userPic.getId());
System.out.println(userPic.getName());
System.out.println(userPic.getBio());
System.out.println(userPic.getPic().length);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
4.3 传入多个输入参数
MyBatis中的映射语句有一个parameterType属性来制定输入参数的类型。如果我们想给映射语句传入多个参数的话,我们可以将所有的输入参数放到HashMap中,将HashMap传递给映射语句。同时MyBatis还提供了另外一种传递多个输入参数给映射语句的方法。假设我们想通过给定的name和email信息查找学生信息,定义查询接口如下:
对于映射器中的方法,MyBatis默认从左到右给方法的参数命名为param1、param2…,依次类推。
public interface StudentMapper{
List<Student> findAllStudentsByNameEmail(String name, String email);
}
MyBatis支持将多个输入参数传递给映射语句,并以#{param}的语法形式引用它们:
<select id="findAllStudentsByNameEmail" resultMap="StudentResult">
select stud_id, name,email, phone from Students
where name=#{param1} and email=#{param2}
</select>
这里#{param1}引用第一个参数name,而#{param2}引用了第二个参数email。
代码中调用:
Student Mapper student Mapper = sql Session.get Mapper(StudentMapper.class);
student Mapper.findAllStudentsByNameEmail(name, email);
4.4 多行结果集映射成Map
可以使用之前我们介绍到的接口的方式来实现(默认把列名作为key,列中的值作为value)。
如果有一些特殊的情况,比如需要使用id值作为key,把一行数据封装成的对象作为value放到map中的话,需要使用下面的方式:
<select id="findAllUsers" resultType="User">
select id,name,gender from t_user
</select>
Map<Integer, User> map = session.selectMap("com.briup.mappers.SpecialMapper.findAllUsers","id");
for(Integer key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
注意:需要注意gender列的值都是数字还是都是字符串(需要一致)
这里map将会将id作为key值,而每行数据封装成的User对象作为value值。
4.5 使用RowBounds对结果集进行分页
有时候,我们会需要跟海量的数据打交道,比如一个有数百万条数据级别的表。由于计算机内存的现实我们不可能一次性加载这么多数据,我们可以获取到数据的一部分。特别是在Web应用程序中,分页机制被用来以一页一页的形式展示海量的数据。
MyBatis可以使用RowBounds逐页加载表数据。RowBounds对象可以使用offset和limit参数来构建。参数offset表示开始位置,而limit表示要取的记录的数目
<select id="findAllUsers" resultType="User">
select id,name,gender from t_user
</select>
public List<User> findAllUsers(RowBounds rowBounds);
然后,你可以加载第一页数据(前5条):
int offset = 0;
int limit = 5;
RowBounds rowBounds = new RowBounds(offset, limit);
List<Student> = studentMapper.getStudents(rowBounds);
若要展示第二页,使用offset=5,limit=5
但是其实Mybatis的分页是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页效率会很低。
oracle使用rownum也可以完成分页:
rownum 只能等于1
rownum 大于0
rownum 可以小于任何数
例如:把sql语句查询结果当做一张表再查询
select *
from (
select rownum as rowno, t.*
from t_user t
where rownum <= 10
) temp
where temp.rowno >= 5;
4.6 使用ResultHandler自定义结果集ResultSet处理
MyBatis在将查询结果集映射到java对象方面提供了很大的选择性。但是,有时候我们会遇到由于特定的目的,需要我们自己处理SQL查询结果的情况。MyBatis提供了ResultHandler接口,可以让我们以任何自己喜欢的方式处理结果集ResultSet。
例如:我们要把t_user表中所有数据的id和name查询出来,并且把id值作为key,把name值作为value封装到Map集合中
注意:sqlSession.selectMap()则可以返回以给定列为key,记录对象为value的map。但是不能将其配置成使用其中一个属性作为key,而另外的属性作为 value。但是mybatis在之后的版本中可能会完成这个功能
对于sqlSession.select()方法,我们可以传递给它一个ResultHandler接口的实现,它会被调用来处理ResultSet的每一条记录,而且完成我们上面的需求:
@Test
public void test_ResultHandler(){
final Map<Integer,String> map = new HashMap<Integer, String>();
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
session.select("com.briup.mappers.SpecialMapper.findAllUsers", new ResultHandler<User>() {
@Override
public void handleResult(ResultContext<? extends User> resultContext) {
User user = resultContext.getResultObject();
map.put(user.getId(), user.getName());
}
});
for(Integer key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
在上述的代码中,我们提供了匿名内部类对ResultHandler接口的实现,在handleResult()方法中,我们使用context.getResultObject()获取当前的result对象,即User对象,并对查询返回的每一行都会调用handleResult()方法,从而我们从User对象中取出id和name的值,将其放到map中。
4.7 缓存
将从数据库中加载的数据缓存到内存中,是很多应用程序为了提高性能而采取的一贯做法。默认情况下,mybatis会启用一级缓存;即,如果你使用同一个SqlSession接口对象调用了相同的SELECT语句,则直接会从缓存中返回结果,而不是再查询一次数据库。
注意:session调用commit或close方法后,一级缓存就会被清空
例如: 根据日志输出可以看出,下面代码只会发出一条sql查询语句
@Test
public void test_cache1(){
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
User user1 = mapper.findUserById(21);
System.out.println(user1);
// session.commit();
User user2 = mapper.findUserById(21);
System.out.println(user2);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
二级缓存: 在不同的session对象之间可以共享缓存数据
1.mybatis-config.xml文件中保证<setting name="cacheEnabled" value="true"/>设置中是缓存功能是开启的,默认就是开启的true
2.在需要二级缓存的xml映射文件中,手动开启缓存功能,在根元素中加入一个标签即可:<cache/>
3.一个session查询完数据之后,需要调用commit或者close方法后,这个数据才会进入到缓存中,然后其他session就可以共享到这个缓存数据了
注意:默认情况下,被二级缓存保存的对象需要实现序列化接口,可以通过cache标签的readOnly属性进行设置
例如:
mybatis-config.xml:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
xml映射文件:
<mapper namespace="com.briup.mappers.SpecialMapper">
<cache/>
<select> ..</select>
<select> ..</select>
<select> ..</select>
</mapper>
测试代码:
@Test
public void test_cache2(){
SqlSession session1 = null;
SqlSession session2 = null;
try {
session1 = MyBatisSqlSessionFactory.openSession();
session2 = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper1 = session1.getMapper(SpecialMapper.class);
SpecialMapper mapper2 = session2.getMapper(SpecialMapper.class);
User user1 = mapper1.findUserById(21);
System.out.println(user1);
session1.commit();
User user2 = mapper2.findUserById(21);
System.out.println(user2);
session2.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session1!=null)session1.close();
if(session2!=null)session2.close();
}
}
二级缓存补充说明
1. 映射语句文件中的所有select语句将会被缓存
2. 映射语句文件中的所有insert,update和delete语句会刷新缓存
3. 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
4. 缓存会根据指定的时间间隔来刷新。
5. 缓存会存储1024个对象
cache标签常用属性:
<cache
eviction="FIFO" <!--回收策略为先进先出-->
flushInterval="60000" <!--自动刷新时间60s-->
size="512" <!--最多缓存512个引用对象-->
readOnly="true"/> <!--true表示对象不能被写出去,只能读数据,即不可以被序列化,false表示可以写出去,即可以被序列化,默认值是false-->
五 MyBatis中的注解
之前我们都是在映射器MapperXML配置文件中配置映射语句的。除此之外MyBatis也支持使用注解来配置映射语句。当我们使用基于注解的映射器接口时,我们不再需要在XML配置文件中配置了。如果你愿意,你也可以同时使用基于XML和基于注解的映射语句。
5.1 在映射器Mapper接口上使用注解
MyBatis对于大部分的基于XML的映射器元素(包括<select>,<update>)提供了对应的基于注解的配置项。然而在某些情况下,基于注解配置还不能支持基于XML的一些元素。
5.2 映射语句
MyBatis提供了多种注解来支持不同类型的语句如:SELECT,INSERT,UPDATE,DELETE。
5.2.1 @Insert
我们可以使用 @Insert注解来定义一个INSERT映射语句:
package com.briup.mappers;
public interface StudentMapper{
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addr Id},#{phone})")
int insertStudent(Student student);
}
自动生成主键
可以使用 @Options注解的userGeneratedKeys和keyProperty属性让数据库产生 auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addr Id},#{phone})")
@Options(useGeneratedKeys = true, keyProperty = "studId")
int insertStudent(Student student);
这里STUD_ID列值将会通过MySQL数据库自动生成。并且生成的值将会被设置到student对象的studId属性中。
StudentMapper mapper = sqlSession.get Mapper(Student Mapper.class);
mapper.insertStudent(student);
int studentId = student.getStudId();
有一些数据库如Oracle,并不支持AUTO_INCREMENT列属性,它使用序列(SEQUENCE)来产生主键的值。
我们可以使用 @SelectKey注解来为任意SQL语句来指定主键值,作为主键列的值。假设我们有一个名为STUD_ID_SEQ的序列来生成STUD_ID主键值。
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.NEXTVAL FROM DUAL",
keyProperty="studId", resultType=int.class, before=true)
int insertStudent(Student student);
这里我们使用了 @Select Key来生成主键值,并且存储到了student对象的studId属性上。由于我们设置了before=true,该语句将会在执行INSERT语句之前执行。如果你使用序列作为触发器来设置主键值,我们可以在INSERT语句执行后,从sequence_name.currval获取数据库产生的主键值。
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.CURRVAL FROM DUAL",
keyProperty="studId", resultType=int.class, before=false)
int insertStudent(Student student);
5.2.2 @Update
我们可以使用 @Update注解来定义一个UPDATE映射语句,如下所示:
@Update("UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email},
PHONE=#{phone} WHERE STUD_ID=#{studId}")
int updateStudent(Student student);
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
5.2.3 @Delete
我们可以使用 @Delete注解来定义一个DELETE映射语句,如下所示:
@Delete("DELETE FROM STUDENTS WHERE STUD_ID=#{studId}")
int deleteStudent(int studId);
5.2.4 @Select
我们可以使用 @Select注解来定义一个SELECT映射语句。
package com.briup.mappers;
public interface StudentMapper{
@Select("SELECT STUD_ID AS STUDID, NAME, EMAIL, PHONE FROM
STUDENTS WHERE STUD_ID=#{studId}")
Student findStudentById(Integer studId);
}
为了将列名和Student bean属性名匹配,我们为stud_id起了一个studId的别名。如果返回了多行结果,将抛出TooManyResultsException异常。
5.3 结果映射
我们可以将查询结果通过别名或者是 @Results注解与Java Bean属性映射起来。
package com.briup.mappers;
public interface StudentMapper{
@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
}
注意:
@Results注解和映射器XML配置文件元素<resultMap> 对应。然而,My Batis3.2.2不能为 @Results注解赋予一个ID。所以,不像<resultMap>元素,我们不应在不同的映射语句中重用 @Results声明。这意味着即使 @Results注解完全相同,我们也需要(在不同的映射接口中)重复 @Results声明。
例如,看下面的 findStudentById()和 findAllStudents()方法:
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
Student findStudentById(int studId);
@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
这里两个语句的 @Results配置完全相同,但是必须得重复它。这里有一个解决方法。我们可以创建一个映射器Mapper配置文件,然后配置<resultMap>元素,然后使用 @ResultMap注解引用此<resultMap>。 在StudentMapper.xml中定义一个ID为StudentResult的<resultMap>。
xml配置:
<mapper namespace="com.briup.mappers.StudentMapper">
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
</mapper>
在StudentMapper.java中,使用 @Result Map引用名为StudentResult的resultMap:
public interface StudentMapper{
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@ResultMap("com.briup.mappers.StudentMapper.StudentResult")
Student findStudentById(int stud Id);
@Select("SELECT * FROM STUDENTS")
@ResultMap("com.briup.mappers.StudentMapper.StudentResult")
List<Student> findAllStudents();
}
5.4 一对一映射
MyBatis提供了 @One注解来使用嵌套select语句(Nested-Select)加载一对一关联查询数据。让我们看看怎样使用 @One注解获取学生及其地址信息
public interface StudentMapper{
@Select("SELECT ADDR_ID AS ADDRID, STREET, CITY, STATE, ZIP, COUNTRY FROM ADDRESSES WHERE ADDR_ID=#{id}")
Address findAddressById(int id);
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} ")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = "com.briup.mappers.Student Mapper.findAddressById"))
})
Student selectStudentWithAddress(int studId);
}
这里我们使用了 @One注解的select属性来指定一个使用了完全限定名的方法上,该方法会返回一个Address对象。使用column=”addr_id”,则STUEDNTS表中列addr_id的值将会作为输入参数传递给find Address By Id()方法。如果 @One SELECT查询返回了多行结果,则会抛出TooManyResultsException异常。
int studId = 1;
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudentWithAddress(studId);
System.out.println("Student :"+student);
System.out.println("Address :"+student.getAddress());
我们可以通过基于XML的映射器配置,使用嵌套结果ResultMap来加载一对一关联的查询。而MyBatis3.2.2 版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置<resultMap>并且使用 @ResultMap注解来引用它。
在StudentMapper.xml中配置<resultMap>,如下所示:
<mapper namespace="com.briup.mappers.StudentMapper">
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>
</mapper>
public interface StudentMapper{
@Select("select stud_id, name, email, a.addr_id, street, city, state, zip, country" + " FROM students s left outer join addresses a on s.addr_id=a.addr_id" + " where stud_id=#{studId} ")
@ResultMap("com.briup.mappers.StudentMapper.StudentWithAddressResult")
Student selectStudentWithAddress(int id);
}
5.5 一对多映射
MyBatis提供了 @Many注解,用来使用嵌套Select语句加载一对多关联查询。现在让我们看一下如何使用 @Many注解获取一个讲师及其教授课程列表信息:
public interface TutorMapper{
@Select("select addr_id as addrId, street, city, state, zip,
country from addresses where addr_id=#{id}")
Address findAddressById(int id);
@Select("select * from courses where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "course_id", property = "courseId"),
@Result(column = "name", property = "name"),
@Result(column = "description", property = "description"),
@Result(column = "start_date" property = "startDate"),
@Result(column = "end_date" property = "endDate")
})
List<Course> findCoursesByTutorId(int tutorId);
@Select("SELECT tutor_id, name as tutor_name, email, addr_id
FROM tutors where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "tutor_id", property = "tutorId"),
@Result(column = "tutor_name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = "com.briup.mappers.Tutor Mapper.findAddressById")),
@Result(property = "courses", column = "tutor_id",
many = @Many(select = "com.briup.mappers.Tutor Mapper.findCoursesByTutorId"))
})
Tutor findTutorById(int tutorId);
}
这里我们使用了 @Many注解的select属性来指向一个完全限定名称的方法,该方法将返回一个 List<Course>对象。使用column=”tutor_id”,TUTORS表中的tutor_id列值将会作为输入参数传递给find Courses By Tutor Id()方法。
之前我们使用过嵌套结果ResultMap来加载一对多关联的查询。而MyBatis3.2.2版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置<result Map>并且使用 @ResultMap注解来引用它。
在TutorMapper.xml中配置<resultMap>,如下所示:
<mapper namespace="com.briup.mappers.Tutor Mapper">
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="course Id" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<association property="address" result Map="AddressResult" />
<collection property="courses" result Map="CourseResult" />
</resultMap>
</mapper>
public interface TutorMapper{
@Select("SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL,
A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY, COURSE_ID, C.NAME,
DESCRIPTION, START_DATE, END_DATE FROM TUTORS T LEFT OUTER
JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID LEFT OUTER JOIN COURSES
C ON T.TUTOR_ID=C.TUTOR_ID WHERE T.TUTOR_ID=#{tutorId}")
@Result Map("com.briup.mappers.TutorMapper.TutorResult")
Tutor selectTutorById(int tutorId);
}
有时候,静态的SQL语句并不能满足应用程序的需求。我们可以根据一些条件,来动态地构建 SQL语句。
例如,在Web应用程序中,有可能有一些搜索界面,需要输入一个或多个选项,然后根据这些已选择的条件去执行检索操作。在实现这种类型的搜索功能,我们可能需要根据这些条件来构建动态的SQL语句。如果用户提供了任何输入条件,我们需要将那个条件添加到SQL语句的WHERE子句中。MyBatis通过使用<if>,<choose>,<where>,<foreach>,<trim>元素提供了对构造动态SQL语句的高级别支持。
DynamicSql
3.6.1 If 条件
<if>元素被用来有条件地嵌入SQL片段,如果测试条件被赋值为true,则相应地SQL片段将会被添加到SQL语句中。假定我们有一个课程搜索界面,设置了讲师(Tutor)下拉列表框,课程名称(CourseName)文本输入框,开始时间(StartDate)输入框,结束时间(EndDate)输入框,作为搜索条件。假定课讲师下拉列表是必须选的,其他的都是可选的。
当用户点击搜索按钮时,我们需要显示符合以下条件的成列表:
特定讲师的课程
课程名
包含输入的课程名称关键字的课程;如果课程名称输入为空,则取所有课程
在开始时间和结束时间段内的课程
我们可以对应的映射语句,如下所示:
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
WHERE TUTOR_ID= #{tutorId}
<if test="courseName != null">
AND NAME LIKE #{courseName}
</if>
<if test="startDate != null">
AND START_DATE >= #{startDate}
</if>
<if test="endDate != null">
AND END_DATE <![CDATA[ <= ]]> #{endDate}
</if>
</select>
public interface DynamicSqlMapper{
List<Course> searchCourses(Map<String, Object> map);
}
public void searchCourses(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("tutorId", 1);
map.put("courseName", "%Java%");
map.put("startDate", new Date());
DynamicSqlMapper mapper = sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses = mapper.searchCourses(map);
for (Course course : courses){
System.out.println(course);
}
}
此处将生成查询语句SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like ? AND START_DATE >= ?。
3.6.2 choose,when 和 otherwise 条件
有时候,查询功能是以查询类别为基础的。首先,用户需要先选择是否希望通过选择讲师,课程名称,开始时间,或结束时间作为查询条件类别来进行查询,然后根据选择的查询类别,输入相应的参数。在这样的情景中,我们【需要只使用其中一种】查询类别。
MyBatis提供了<choose>元素支持此类型的SQL预处理。
如果没有选择查询类别,则查询开始时间在今天之后的课程,代码如下:
注意:mysql中now()是当前时间 oracle需要使用sysdate
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<choose>
<when test="searchBy == 'Tutor'">
WHERE TUTOR_ID= #{tutorId}
</when>
<when test="searchBy == 'CourseName'">
WHERE name like #{courseName}
</when>
<otherwise>
WHERE start_date >= sysdate
</otherwise>
</choose>
</select>
MyBatis计算<choose>测试条件的值,且使用第一个值为TRUE的子句。如果没有条件为 true,则使用<otherwise>内的子句。
相当于我们常用的java代码中的这个例子:
if(){
..
}
else if(){
..
}
else{
..
}
测试例子:public void test_when(){
try {
SqlSession sqlSession=
MyBatisSqlSessionFactory.openSession();
Map<String,Object> map = new HashMap<String,Object>();
map.put("tutorId", 1);
map.put("courseName", "%Java%");
//输入根据什么类型来查询
map.put("searchBy", "Tutor");
//map.put("searchBy", "CourseName");
//map.put("startDate", new Date());
DynamicSqlMapper mapper =
sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses =
mapper.searchCourses(map);
for (Course course : courses){
System.out.println(course);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3.6.3 Where 条件
有时候,所有的查询条件应该是可选的。在需要使用至少一种查询条件的情况下,我们应该使用WHERE子句。并且如果有多个条件,我们需要在条件中添加AND或OR。
MyBatis提供了<where>元素支持这种类型的动态SQL语句。
在我们查询课程界面,我们假设所有的查询条件是可选的。然而,当需要提供一个或多个查询条件时,应该改使用WHERE子句。
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<where>
<if test="tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
<if test="startDate != null">
AND start_date >= #{startDate}
</if>
<if test="endDate != null">
AND end_date <![CDATA[ <= ]]> #{endDate}
</if>
</where>
</select>
<where>元素只有在其内部标签有返回内容时才会在动态语句上插入WHERE条件语句。并且,如果WHERE子句以AND或者OR打头,则打头的AND或OR将会被移除。例如:如果参数tutorId的值为null,并且courseName参数值不为null,则<where>标签会将AND name like #{courseName}中的AND移除掉,生成的SQL WHERE子句为:
where name like #{courseName}
3.6.4 <trim>条件
<trim>元素和<where>元素类似,但是<trim>提供了在添加前缀/后缀或者移除前缀/后缀方面提供更大的灵活性。
<select id="searchCourses" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<trim prefix="WHERE" suffixOverrides="or | and">
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId} and
</if>
<if test="courseName != null">
name like #{courseName} and
</if>
</trim>
</select>
prefix表示有一个if成立则插入where语句
suffix表示后缀,和prefix相反
suffixOverrides="and"表示如果最后生成的sql语句多一个and,则自动去掉.
prefixOverrides的意思是处理前缀,和suffixOverrides相反
这里如果任意一个<if>条件为true,<trim>元素会插入WHERE,并且移除紧跟WHERE后面的AND
3.6.5 foreach 循环
另外一个强大的动态SQL语句构造标签即是<foreach>。它可以迭代遍历一个数组或者列表,构造AND/OR条件或一个IN子句。
假设我们想找到tutor_id为 1,3,6的讲师所教授的课程,我们可以传递一个tutor_id组成的列表给映射语句,然后通过<foreach>遍历此列表构造动态SQL。
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
<foreach item="tutorId" collection="tutorIds">
OR tutor_id=#{tutorId}
</foreach>
</where>
</if>
</select>
代码:
public interface DynamicSqlMapper{
List<Course> searchCoursesByTutors(Map<String,Object> map);
}
public void searchCoursesByTutors(){
Map<String,Object> map = new HashMap<String,Object>();
List<Integer> tutorIds = new ArrayList<Integer>();
tutorIds.add(1);
tutorIds.add(3);
tutorIds.add(6);
map.put("tutorIds", tutorIds);
DynamicSqlMapper mapper =
sqlSession.getMapper(DynamicSqlMapper.class);
List<Course> courses = mapper.searchCoursesByTutors(map);
for (Course course : courses){
System.out.println(course);
}
}
怎样使用<foreach>生成IN子句:
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
tutor_id IN
<foreach item="tempValue" collection="tutorIds" open="(" separator="," close=")">
#{tempValue}
</foreach>
</where>
</if>
</select>
3.6.6 set 条件
<set>元素和<where>元素类似,如果其内部条件判断有任何内容返回时,他会插入SET SQL 片段。
<update id="updateCourse" parameterType="Course">
update courses
<set>
<if test="name != null">name=#{name},</if>
<if test="description != null">description=#{description},</if>
<if test="startDate != null">start_date=#{startDate},</if>
<if test="endDate != null">end_date=#{endDate},</if>
</set>
where course_id=#{courseId}
</update>
这里,如果<if>条件返回了任何文本内容,<set>将会插入set关键字和其文本内容,并且会剔除将末尾的“,”。在上述的例子中,如果phone!=null,<set>将会让会移除phone=#{phone}后的逗号“,”,生成set phone=#{phone}
四 mybatis的一些特殊功能 special
除了简化数据库编程外,MyBatis还提供了各种功能,这些对实现一些常用任务非常有用,比如按页加载表数据,存取CLOB/BLOB类型的数据,处理枚举类型值,等等。
4.1 处理枚举类型
MyBatis支持持久化enum类型属性。假设t_user表中有一列gender(性别)类型为 varchar2(10),存储 MALE 或者 FEMALE 两种值。并且,Student对象有一个enum类型的gender 属性,如下所示:
public enum Gender {
FEMALE,MALE
}
默认情况下MyBatis使用EnumTypeHandler来处理enum类型的Java属性,并且将其存储为 enum值的名称。你不需要为此做任何额外的配置。你可以可以向使用基本数据类型属性一样使用enum类型属性,如下:
drop table t_user;
create table t_user(
id number primary key,
name varchar2(50),
gender varchar2(10)
);
public class User{
private Integer id;
private String name;
private Gender gender;
//setters and getters
}
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select my_seq.nextval from dual
</selectKey>
insert into t_user(id,name,gender)
values(#{id},#{name},#{gender})
</insert>
当你执行insertStudent语句的时候MyBatis会取Gender枚举(FEMALE/MALE)的名称,然后将其存储到GENDER列中。如果你希望存储原enum的顺序位置(0/1),而不是enum名,你需要明确地配置它
如果你想存储FEMALE为0,MALE为1到gender列中,你需要在mybatis-config.xml文件中配置EnumOrdinalTypeHandler:
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.briup.special.Gender"/>
注意:使用顺序位置为值存储到数据库时要当心。顺序值是根据enum中的声明顺序赋值的。如果你改变了Gender里面对象的声明顺序,则数据库存储的数据和此顺序值就不匹配了。练习:查询出来看看枚举是什么样的?
4.2 处理CLOB/BLOB类型数据
BLOB和CLOB都是大字段类型,BLOB是按二进制来存储的,而CLOB是可以直接存储文字的。通常像图片、文件、音乐等信息就用BLOB字段来存储,先将文件转为二进制再存储进去。而像文章或者是较长的文字,就用CLOB存储.
BLOB和CLOB在不同的数据库中对应的类型也不一样:
MySQL 中:clob对应text/longtext,blob对应blob
Oracle中:clob对应clob,blob对应blob
MyBatis提供了内建的对CLOB/BLOB类型列的映射处理支持。
drop table user_pics;
create table user_pics(
id number primary key,
name varchar2(50) ,
pic blob,
bio clob
);
这里,照片可以是PNG,JPG或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,MyBatis将CLOB类型的列映射到java.lang.String类型上、而把BLOB列映射到byte[]类型上。
public class UserPic{
private int id;
private String name;
private byte[] pic;
private String bio;
//setters & getters
}
<insert id="insertUserPic" parameterType="UserPic">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select my_seq.nextval from dual
</selectKey>
insert into user_pics(id,name, pic,bio)
values(#{id},#{name},#{pic},#{bio})
</insert>
<select id="getUserPicById" parameterType="int" resultType="UserPic">
select * from user_pics where id=#{id}
</select>
java代码:
@Test
public void test_insertUserPic(){
byte[] pic = null;
try {
//读取用户头像图片
File file = new File("src/com/briup/special/test.png");
InputStream is = new FileInputStream(file);
pic = new byte[is.available()];
is.read(pic);
is.close();
} catch (Exception e){
e.printStackTrace();
}
String name = "tom";
String bio = "可以是很长的字符串";
//准备好要插入到数据库中的数据并封装成对象
UserPic userPic = new UserPic(name, pic , bio);
SqlSession session = null;
try{
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
mapper.insertUserPic(userPic);
session.commit();
}catch (Exception e) {
e.printStackTrace();
session.rollback();
}finally {
if(session!=null)session.close();
}
}
下面的getUserPic()方法展示了怎样将CLOB类型数据读取到String类型,BLOB类型数据读取成byte[]属性:
@Test
public void test_getUserPicById(){
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
UserPic userPic = mapper.getUserPicById(24);
System.out.println(userPic.getId());
System.out.println(userPic.getName());
System.out.println(userPic.getBio());
System.out.println(userPic.getPic().length);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
4.3 传入多个输入参数
MyBatis中的映射语句有一个parameterType属性来制定输入参数的类型。如果我们想给映射语句传入多个参数的话,我们可以将所有的输入参数放到HashMap中,将HashMap传递给映射语句。同时MyBatis还提供了另外一种传递多个输入参数给映射语句的方法。假设我们想通过给定的name和email信息查找学生信息,定义查询接口如下:
对于映射器中的方法,MyBatis默认从左到右给方法的参数命名为param1、param2…,依次类推。
public interface StudentMapper{
List<Student> findAllStudentsByNameEmail(String name, String email);
}
MyBatis支持将多个输入参数传递给映射语句,并以#{param}的语法形式引用它们:
<select id="findAllStudentsByNameEmail" resultMap="StudentResult">
select stud_id, name,email, phone from Students
where name=#{param1} and email=#{param2}
</select>
这里#{param1}引用第一个参数name,而#{param2}引用了第二个参数email。
代码中调用:
Student Mapper student Mapper = sql Session.get Mapper(StudentMapper.class);
student Mapper.findAllStudentsByNameEmail(name, email);
4.4 多行结果集映射成Map
可以使用之前我们介绍到的接口的方式来实现(默认把列名作为key,列中的值作为value)。
如果有一些特殊的情况,比如需要使用id值作为key,把一行数据封装成的对象作为value放到map中的话,需要使用下面的方式:
<select id="findAllUsers" resultType="User">
select id,name,gender from t_user
</select>
Map<Integer, User> map = session.selectMap("com.briup.mappers.SpecialMapper.findAllUsers","id");
for(Integer key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
注意:需要注意gender列的值都是数字还是都是字符串(需要一致)
这里map将会将id作为key值,而每行数据封装成的User对象作为value值。
4.5 使用RowBounds对结果集进行分页
有时候,我们会需要跟海量的数据打交道,比如一个有数百万条数据级别的表。由于计算机内存的现实我们不可能一次性加载这么多数据,我们可以获取到数据的一部分。特别是在Web应用程序中,分页机制被用来以一页一页的形式展示海量的数据。
MyBatis可以使用RowBounds逐页加载表数据。RowBounds对象可以使用offset和limit参数来构建。参数offset表示开始位置,而limit表示要取的记录的数目
<select id="findAllUsers" resultType="User">
select id,name,gender from t_user
</select>
public List<User> findAllUsers(RowBounds rowBounds);
然后,你可以加载第一页数据(前5条):
int offset = 0;
int limit = 5;
RowBounds rowBounds = new RowBounds(offset, limit);
List<Student> = studentMapper.getStudents(rowBounds);
若要展示第二页,使用offset=5,limit=5
但是其实Mybatis的分页是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页效率会很低。
oracle使用rownum也可以完成分页:
rownum 只能等于1
rownum 大于0
rownum 可以小于任何数
例如:把sql语句查询结果当做一张表再查询
select *
from (
select rownum as rowno, t.*
from t_user t
where rownum <= 10
) temp
where temp.rowno >= 5;
4.6 使用ResultHandler自定义结果集ResultSet处理
MyBatis在将查询结果集映射到java对象方面提供了很大的选择性。但是,有时候我们会遇到由于特定的目的,需要我们自己处理SQL查询结果的情况。MyBatis提供了ResultHandler接口,可以让我们以任何自己喜欢的方式处理结果集ResultSet。
例如:我们要把t_user表中所有数据的id和name查询出来,并且把id值作为key,把name值作为value封装到Map集合中
注意:sqlSession.selectMap()则可以返回以给定列为key,记录对象为value的map。但是不能将其配置成使用其中一个属性作为key,而另外的属性作为 value。但是mybatis在之后的版本中可能会完成这个功能
对于sqlSession.select()方法,我们可以传递给它一个ResultHandler接口的实现,它会被调用来处理ResultSet的每一条记录,而且完成我们上面的需求:
@Test
public void test_ResultHandler(){
final Map<Integer,String> map = new HashMap<Integer, String>();
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
session.select("com.briup.mappers.SpecialMapper.findAllUsers", new ResultHandler<User>() {
@Override
public void handleResult(ResultContext<? extends User> resultContext) {
User user = resultContext.getResultObject();
map.put(user.getId(), user.getName());
}
});
for(Integer key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
在上述的代码中,我们提供了匿名内部类对ResultHandler接口的实现,在handleResult()方法中,我们使用context.getResultObject()获取当前的result对象,即User对象,并对查询返回的每一行都会调用handleResult()方法,从而我们从User对象中取出id和name的值,将其放到map中。
4.7 缓存
将从数据库中加载的数据缓存到内存中,是很多应用程序为了提高性能而采取的一贯做法。默认情况下,mybatis会启用一级缓存;即,如果你使用同一个SqlSession接口对象调用了相同的SELECT语句,则直接会从缓存中返回结果,而不是再查询一次数据库。
注意:session调用commit或close方法后,一级缓存就会被清空
例如: 根据日志输出可以看出,下面代码只会发出一条sql查询语句
@Test
public void test_cache1(){
SqlSession session = null;
try {
session = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper = session.getMapper(SpecialMapper.class);
User user1 = mapper.findUserById(21);
System.out.println(user1);
// session.commit();
User user2 = mapper.findUserById(21);
System.out.println(user2);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session!=null)session.close();
}
}
二级缓存: 在不同的session对象之间可以共享缓存数据
1.mybatis-config.xml文件中保证<setting name="cacheEnabled" value="true"/>设置中是缓存功能是开启的,默认就是开启的true
2.在需要二级缓存的xml映射文件中,手动开启缓存功能,在根元素中加入一个标签即可:<cache/>
3.一个session查询完数据之后,需要调用commit或者close方法后,这个数据才会进入到缓存中,然后其他session就可以共享到这个缓存数据了
注意:默认情况下,被二级缓存保存的对象需要实现序列化接口,可以通过cache标签的readOnly属性进行设置
例如:
mybatis-config.xml:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
xml映射文件:
<mapper namespace="com.briup.mappers.SpecialMapper">
<cache/>
<select> ..</select>
<select> ..</select>
<select> ..</select>
</mapper>
测试代码:
@Test
public void test_cache2(){
SqlSession session1 = null;
SqlSession session2 = null;
try {
session1 = MyBatisSqlSessionFactory.openSession();
session2 = MyBatisSqlSessionFactory.openSession();
SpecialMapper mapper1 = session1.getMapper(SpecialMapper.class);
SpecialMapper mapper2 = session2.getMapper(SpecialMapper.class);
User user1 = mapper1.findUserById(21);
System.out.println(user1);
session1.commit();
User user2 = mapper2.findUserById(21);
System.out.println(user2);
session2.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
if(session1!=null)session1.close();
if(session2!=null)session2.close();
}
}
二级缓存补充说明
1. 映射语句文件中的所有select语句将会被缓存
2. 映射语句文件中的所有insert,update和delete语句会刷新缓存
3. 缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
4. 缓存会根据指定的时间间隔来刷新。
5. 缓存会存储1024个对象
cache标签常用属性:
<cache
eviction="FIFO" <!--回收策略为先进先出-->
flushInterval="60000" <!--自动刷新时间60s-->
size="512" <!--最多缓存512个引用对象-->
readOnly="true"/> <!--true表示对象不能被写出去,只能读数据,即不可以被序列化,false表示可以写出去,即可以被序列化,默认值是false-->
五 MyBatis中的注解
之前我们都是在映射器MapperXML配置文件中配置映射语句的。除此之外MyBatis也支持使用注解来配置映射语句。当我们使用基于注解的映射器接口时,我们不再需要在XML配置文件中配置了。如果你愿意,你也可以同时使用基于XML和基于注解的映射语句。
5.1 在映射器Mapper接口上使用注解
MyBatis对于大部分的基于XML的映射器元素(包括<select>,<update>)提供了对应的基于注解的配置项。然而在某些情况下,基于注解配置还不能支持基于XML的一些元素。
5.2 映射语句
MyBatis提供了多种注解来支持不同类型的语句如:SELECT,INSERT,UPDATE,DELETE。
5.2.1 @Insert
我们可以使用 @Insert注解来定义一个INSERT映射语句:
package com.briup.mappers;
public interface StudentMapper{
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addr Id},#{phone})")
int insertStudent(Student student);
}
自动生成主键
可以使用 @Options注解的userGeneratedKeys和keyProperty属性让数据库产生 auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addr Id},#{phone})")
@Options(useGeneratedKeys = true, keyProperty = "studId")
int insertStudent(Student student);
这里STUD_ID列值将会通过MySQL数据库自动生成。并且生成的值将会被设置到student对象的studId属性中。
StudentMapper mapper = sqlSession.get Mapper(Student Mapper.class);
mapper.insertStudent(student);
int studentId = student.getStudId();
有一些数据库如Oracle,并不支持AUTO_INCREMENT列属性,它使用序列(SEQUENCE)来产生主键的值。
我们可以使用 @SelectKey注解来为任意SQL语句来指定主键值,作为主键列的值。假设我们有一个名为STUD_ID_SEQ的序列来生成STUD_ID主键值。
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.NEXTVAL FROM DUAL",
keyProperty="studId", resultType=int.class, before=true)
int insertStudent(Student student);
这里我们使用了 @Select Key来生成主键值,并且存储到了student对象的studId属性上。由于我们设置了before=true,该语句将会在执行INSERT语句之前执行。如果你使用序列作为触发器来设置主键值,我们可以在INSERT语句执行后,从sequence_name.currval获取数据库产生的主键值。
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.CURRVAL FROM DUAL",
keyProperty="studId", resultType=int.class, before=false)
int insertStudent(Student student);
5.2.2 @Update
我们可以使用 @Update注解来定义一个UPDATE映射语句,如下所示:
@Update("UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email},
PHONE=#{phone} WHERE STUD_ID=#{studId}")
int updateStudent(Student student);
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
5.2.3 @Delete
我们可以使用 @Delete注解来定义一个DELETE映射语句,如下所示:
@Delete("DELETE FROM STUDENTS WHERE STUD_ID=#{studId}")
int deleteStudent(int studId);
5.2.4 @Select
我们可以使用 @Select注解来定义一个SELECT映射语句。
package com.briup.mappers;
public interface StudentMapper{
@Select("SELECT STUD_ID AS STUDID, NAME, EMAIL, PHONE FROM
STUDENTS WHERE STUD_ID=#{studId}")
Student findStudentById(Integer studId);
}
为了将列名和Student bean属性名匹配,我们为stud_id起了一个studId的别名。如果返回了多行结果,将抛出TooManyResultsException异常。
5.3 结果映射
我们可以将查询结果通过别名或者是 @Results注解与Java Bean属性映射起来。
package com.briup.mappers;
public interface StudentMapper{
@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
}
注意:
@Results注解和映射器XML配置文件元素<resultMap> 对应。然而,My Batis3.2.2不能为 @Results注解赋予一个ID。所以,不像<resultMap>元素,我们不应在不同的映射语句中重用 @Results声明。这意味着即使 @Results注解完全相同,我们也需要(在不同的映射接口中)重复 @Results声明。
例如,看下面的 findStudentById()和 findAllStudents()方法:
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
Student findStudentById(int studId);
@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
这里两个语句的 @Results配置完全相同,但是必须得重复它。这里有一个解决方法。我们可以创建一个映射器Mapper配置文件,然后配置<resultMap>元素,然后使用 @ResultMap注解引用此<resultMap>。 在StudentMapper.xml中定义一个ID为StudentResult的<resultMap>。
xml配置:
<mapper namespace="com.briup.mappers.StudentMapper">
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
</mapper>
在StudentMapper.java中,使用 @Result Map引用名为StudentResult的resultMap:
public interface StudentMapper{
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@ResultMap("com.briup.mappers.StudentMapper.StudentResult")
Student findStudentById(int stud Id);
@Select("SELECT * FROM STUDENTS")
@ResultMap("com.briup.mappers.StudentMapper.StudentResult")
List<Student> findAllStudents();
}
5.4 一对一映射
MyBatis提供了 @One注解来使用嵌套select语句(Nested-Select)加载一对一关联查询数据。让我们看看怎样使用 @One注解获取学生及其地址信息
public interface StudentMapper{
@Select("SELECT ADDR_ID AS ADDRID, STREET, CITY, STATE, ZIP, COUNTRY FROM ADDRESSES WHERE ADDR_ID=#{id}")
Address findAddressById(int id);
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} ")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = "com.briup.mappers.Student Mapper.findAddressById"))
})
Student selectStudentWithAddress(int studId);
}
这里我们使用了 @One注解的select属性来指定一个使用了完全限定名的方法上,该方法会返回一个Address对象。使用column=”addr_id”,则STUEDNTS表中列addr_id的值将会作为输入参数传递给find Address By Id()方法。如果 @One SELECT查询返回了多行结果,则会抛出TooManyResultsException异常。
int studId = 1;
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudentWithAddress(studId);
System.out.println("Student :"+student);
System.out.println("Address :"+student.getAddress());
我们可以通过基于XML的映射器配置,使用嵌套结果ResultMap来加载一对一关联的查询。而MyBatis3.2.2 版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置<resultMap>并且使用 @ResultMap注解来引用它。
在StudentMapper.xml中配置<resultMap>,如下所示:
<mapper namespace="com.briup.mappers.StudentMapper">
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>
</mapper>
public interface StudentMapper{
@Select("select stud_id, name, email, a.addr_id, street, city, state, zip, country" + " FROM students s left outer join addresses a on s.addr_id=a.addr_id" + " where stud_id=#{studId} ")
@ResultMap("com.briup.mappers.StudentMapper.StudentWithAddressResult")
Student selectStudentWithAddress(int id);
}
5.5 一对多映射
MyBatis提供了 @Many注解,用来使用嵌套Select语句加载一对多关联查询。现在让我们看一下如何使用 @Many注解获取一个讲师及其教授课程列表信息:
public interface TutorMapper{
@Select("select addr_id as addrId, street, city, state, zip,
country from addresses where addr_id=#{id}")
Address findAddressById(int id);
@Select("select * from courses where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "course_id", property = "courseId"),
@Result(column = "name", property = "name"),
@Result(column = "description", property = "description"),
@Result(column = "start_date" property = "startDate"),
@Result(column = "end_date" property = "endDate")
})
List<Course> findCoursesByTutorId(int tutorId);
@Select("SELECT tutor_id, name as tutor_name, email, addr_id
FROM tutors where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "tutor_id", property = "tutorId"),
@Result(column = "tutor_name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = "com.briup.mappers.Tutor Mapper.findAddressById")),
@Result(property = "courses", column = "tutor_id",
many = @Many(select = "com.briup.mappers.Tutor Mapper.findCoursesByTutorId"))
})
Tutor findTutorById(int tutorId);
}
这里我们使用了 @Many注解的select属性来指向一个完全限定名称的方法,该方法将返回一个 List<Course>对象。使用column=”tutor_id”,TUTORS表中的tutor_id列值将会作为输入参数传递给find Courses By Tutor Id()方法。
之前我们使用过嵌套结果ResultMap来加载一对多关联的查询。而MyBatis3.2.2版本,并没有对应的注解支持。但是我们可以在映射器Mapper配置文件中配置<result Map>并且使用 @ResultMap注解来引用它。
在TutorMapper.xml中配置<resultMap>,如下所示:
<mapper namespace="com.briup.mappers.Tutor Mapper">
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="course Id" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<association property="address" result Map="AddressResult" />
<collection property="courses" result Map="CourseResult" />
</resultMap>
</mapper>
public interface TutorMapper{
@Select("SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL,
A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY, COURSE_ID, C.NAME,
DESCRIPTION, START_DATE, END_DATE FROM TUTORS T LEFT OUTER
JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID LEFT OUTER JOIN COURSES
C ON T.TUTOR_ID=C.TUTOR_ID WHERE T.TUTOR_ID=#{tutorId}")
@Result Map("com.briup.mappers.TutorMapper.TutorResult")
Tutor selectTutorById(int tutorId);
}