Mybatis学习笔记-四、mybatis的一些特殊功能 special

四、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或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,My Batis将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文件中保证设置中是缓存功能是开启的,默认就是开启的true
2.在需要二级缓存的xml映射文件中,手动开启缓存功能,在根元素中加入一个标签即可:
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-->
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值