mybatis小结
1. mybatis
一个ORM--对象关系映射 框架,内部封装了JDBC,使得开发时只需要关注SQL语句本身,不需要关注加载驱动、获取连接、创建Statement等对象的过程
可通过xml或注解来配置和映射关系,将实体类和数据库记录对应起来,避免了繁杂的JDBC代码和设置参数、获取结果集并封装等过程
2. 编写sql时 #{} 和 ${}
#{} 是编译预处理,而${}是字符串替换
mybatis会将 #{}替换为 ? ,并通过PreparedStatement的方法来设置值,可避免sql注入
3. 实体类的属性名字和数据库表中字段名字不一致
(1)查询的sql语句中字段定义别名,别名与实体类属性名一致
(2)映射器查询标签里面 返回值类型为 resultMap
在resutMap里面建立实体类属性和表字段的对应关系
<select id="findUser" parameterType="string" resultMap="userInfoResMap"> select * from users where id = #{id} </select> <resultMap id="userInfoResMap" type="xx.xx.xxx.User"> <id column="user_id" property="userId" /> <result column="create_time" property="createTime"/> <result column="note" property="note"/> </resultMap>
column为表字段名字,而property为实体类属性名字
4. 模糊查询
(1)java代码中将参数加上 % ,即硬编码
String str = "xt"; String name = "%"+str+"%"; xxx.findByName(value); <select id="findByName"> select * from users where name like ${name} </select>
(2)在sql语句中写
不能在sql语句中直接拼接通配符,即不能:
<select id="xxx"> select * from users where name like '%'${name}'%' </select>
正确写法:①用 concat( ) 函数拼接 ②用 #{}代替${}
<select id="xxx"> select * from users where name like concat('%',#{name},'%') </select>
如果 name 里面有sql语句,concat也会将name里面的内容视作字符串与% 合成一个字符串,不会解析为sql执行
<select id="xxx"> select * from users where name like '%' #{} '%' </select>
#{}之后会用PreparedStatement来执行sql,也可防止注入
5. 映射文件与dao接口
接口的全限定名是映射文件的 namespace 属性值
接口的方法名是映射文件中 Mapper下的标签的id值
Mybatis中,每个select、insert、update、delete标签,会被解析为一个MapperStatement对象,并通过 namespace和标签id唯一定位
接口里面的方法不能重载,因为 只能通过 namespace+方法名 唯一确定一个标签
实现:只有dao接口,而没有实现类,mybatis通过JDK动态代理为Mapper接口生成代理对象proxy
6. 分页
6.1 物理分页与逻辑分页
(1)物理分页
查询 从offset条起,后面的共rows条记录(offset从0开始)
SELECT * FROM table1 LIMIT offset,rows
(2)逻辑分页
RowBounds分页:一次性查出很多记录,然后再这些数据中进行分页查询
RowBounds查询数据:
jdbc驱动中有 Fetch Size 参数,表示每次最多从数据库中查询多少条数据,如果超过了这个数,实际上jdbc自动去再执行了查询。
Mybatis实际上通过多次查询查询出所有数据,然后我们在RowBounds上进行分页查询
如果数据很多,使用RowBounds查询所有数据会有内存溢出风险。所以RowBounds适合数据量不大的查询。
(3)使用RowBounds
原来的接口:
public List<User> findUserByName(String name);
对应的sql--mapper:
<mapper namespace="com.xt.dao.UserDao">
<select id="findUserByName" resultType="com.xt.domain.User">
select * from user where userName like concat('%',#{name},'%')
</select>
</mapper>
使用:
UserDao dao = sqlSession.getMapper(UserDao.class);
List<User> list = dao.findUserByName("name1");
使用RowBounds的接口:
public List<User> findUserByName(String name,RowBounds rowbounds);
sql--mapper不变
使用:
UserDao dao = sqlSession.getMapper(UserDao.class);
List<User> list = dao.findUserByName("name1",new RowBounds(0,5)); //查询 0,1,2,3,4
6.2 分页插件
7. sql结果如何封装为目标对象返回
sql使用字段别名或通过resultMap建立实体类属性与表字段的关系,在查询标签里可通过 resultType,在resultMap里面可通过type属性指定实体类的位置。mybatis通过反射创建对象,然后根据属性与字段的关系对对象的属性逐一赋值
8. 批量插入
(1)方式一
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(ExecutorType.BATCH); UserDao userDao = session.getMapper(UserDao.class); List<String> names = new ArrayList<String>(); names.add("name1"); names.add("name2"); names.add("name3"); for(String name:names) { // int x = userDao.insertNames(name); int x = userDao.insertNames2(name); System.out.println(x); }
映射器:
<insert id="insertNames2"> insert into testBatch2(name) values(#{name}) </insert>
框架来实现批量插入:ExecutorType.BATCH
效果对比:通过mybatis自带的日志输出
不带BATCH的: 带BATCH的:
非批量插入,产生了三条sql,且每次都能获取insert的返回值为1 批量插入,只有一条sql,但不能直接获取insert的返回值
(2)方式2
方式一是对此session下所有操作都改为批量插入,方式二通过改变映射器中sql写法来实现批量插入(foreach标签)
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); //这里没有ExecutorType.BATCH UserDao userDao = session.getMapper(UserDao.class); List<String> names2 = new ArrayList<>(); names2.add("name1"); names2.add("name2"); names2.add("name3"); System.out.println(userDao.insertNames3(names2));
映射器写法:
<insert id="insertNames3"> insert into testBatch2(name) values <foreach collection="list" item="name" separator=","> (#{name}) </foreach> </insert>
效果:
9. 主键的生成和返回(mysql、oracle)
9.1 插入数据时的主键id如何产生
oracle和mysql
①mysql可以设置主键为 auto_increment,插入数据时插入null或者不给此字段赋值即可
②oracle通过序列值作为主键;mysql也可以使用 uuid 来作为主键id
对比:自增主键可能仅适用于部分数据库,但id是递增的,便于排序、查找
uuid 更通用,但不利于排序、查找,且空间消耗大一些
9.2 如何返回插入的主键id值
(1)准备两张表:表testPriKey使用 uuid、表testPriKey2使用自增主键
(2)准备实体类
package com.xt.domain; public class Name { private String id; private String name; public Name(String id,String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(3)使用uuid
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); //通过代理对象调用方法 List<Name> names = new ArrayList<Name>(); names.add(new Name("","name1")); names.add(new Name("","name2")); names.add(new Name("","name3")); for(Name name:names) { int x = userDao.insertNames(name); } //查看返回的主键id for(Name name:names) { System.out.println(name.getId()); } session.commit(); //session.close(); is.close();
映射器:
<insert id="insertNames" parameterType="com.xt.domain.Name"> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> select replace(uuid(),'-','') </selectKey> insert into testPriKey(id,name) values(#{id},#{name}) </insert>
获取id:在insert执行之前(before),查询uuid,这个uuid会写入到 keyProperty上(即Name对象的id属性上),则接下来的insert就能通过 #{id} 获取唯一主键了
返回id:此时List里面,每个Name对象的id字段就有值了
效果:
(4)使用自增主键
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); //通过代理对象调用方法 List<Name> names = new ArrayList<Name>(); names.add(new Name("","name1")); names.add(new Name("","name2")); names.add(new Name("","name3")); for(Name name:names) { int x = userDao.insertNamess(name); System.out.println(x); } /*查看返回的主键id*/ for(Name name:names) { System.out.println(name.getId()); } session.commit(); //session.close(); is.close();
映射器:
<insert id="insertNamess" parameterType="com.xt.domain.Name"> insert into testPriKey2(name) values(#{name}) <selectKey keyProperty="id" order="AFTER" resultType="string"> <!--实体属性id是String类型的--> select LAST_INSERT_ID() </selectKey> </insert>
返回id:在执行每条insert之后(AFTER),通过 select LAST_INSERT_ID() 获取此id值
效果:
10. 在mapper中传递多个参数
准备表:
(1)方式一
表有n个字段需要插值,则方法参数就有n个;并通过@Param注解表明形参和表字段名的关系
//接口声明 int addPerson(@Param("name")String personName, @Param("age") int personAge); //测试方法 InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); PersonDao personDao = session.getMapper(PersonDao.class); //通过代理对象调用方法 personDao.addPerson("name1",10); session.commit(); //session.close(); is.close();
映射器:
<insert id="addPerson"> insert into testTable1(name,age) values(#{name},#{age}) </insert>
(2)方式二
参数很多时,全部放到Map里面
//接口 int addPerson2(Map<String,String> map); InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); PersonDao personDao = session.getMapper(PersonDao.class); //通过代理对象调用方法 Map<String,String> map = new HashMap<>(); map.put("name","name2"); map.put("age","20"); personDao.addPerson2(map); session.commit(); //session.close(); is.close();
映射器:指定参数为map
<insert id="addPerson2" parameterType="map"> insert into testTable1(name,age) values(#{name},#{age}) </insert>
(3)方式三
通过实体类
实体类:
package com.xt.domain; public class Person { private String personName; private int personAge; public Person(String personName, int personAge) { this.personName = personName; this.personAge = personAge; } public String getPersonName() { return personName; } public void setPersonName(String personName) { this.personName = personName; } public int getPersonAge() { return personAge; } public void setPersonAge(int personAge) { this.personAge = personAge; } }
//接口 int addPerson3(Person person); InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(); PersonDao personDao = session.getMapper(PersonDao.class); //通过代理对象调用方法 Person person = new Person("name3",30); personDao.addPerson3(person); session.commit(); //session.close(); is.close();
映射器:参数类型为该实体类
<insert id="addPerson3" parameterType="com.xt.domain.Person"> insert into testTable1(name,age) values(#{personName},#{personAge}) </insert>
11. 动态sql、原理
原理:根据表达式的值完成逻辑判断,然后动态拼接sql
11.1 if
例:模糊查询,传入的username可能传了空,则需要判断入参值然后决定是否拼sql
<select id="findUser" parameterType="string" resultTypeMap="userResultMap">
select user_no,user_name,note from t_user where 1 = 1
<if test="username != null and username != ''">
and user_name like concat('%',#{username},'%')
</if>
</select>
11.2 choose、when、otherwise
<select id="findUser" parameterType="User" resultTypeMap="userResultMap">
select user_no,user_name,note from t_user where 1 = 1
<choose>
<when test="userNo != null and userNo != ''">
AND user_no = #{userNo}
</when>
<when test="userName != null and userName != ''">
AND user_name like concat('%',#{userName},'%')
</when>
<otherwise>
AND note is not null
</otherwise>
</choose>
</select>
11.3 trim、where、set
(1)where:where里面的元素的条件成立时,生成 where 关键字组成sql(这样就不用写 where 1 = 1了)
而且,如果里面语句开头是 AND、OR,也会去掉(比如这里前面的sex为空,而username不为空,则去掉and),然后组成正确的SQL
<select id="findUser" parameterType="string" resultTypeMap="userResultMap">
select user_no,user_name,note from t_user
<where>
<if test="sex != null and sex != ''">
sex like concat('%',#{sex},'%')
</if>
<if test="username != null and username != ''">
and user_name like concat('%',#{username},'%')
</if>
</where>
</select>
(2)trim
trim可以自定义元素的功能
①自定义上面的where元素:
<select id="findUser" parameterType="string" resultTypeMap="userResultMap">
select user_no,user_name,note from t_user
<trim prefix="WHERE" prefixOverride="AND | OR">
<if test="sex != null and sex != ''">
sex like concat('%',#{sex},'%')
</if>
<if test="username != null and username != ''">
and user_name like concat('%',#{username},'%')
</if>
</trim>
</select>
trim里面的子元素不是空的的时候,插入 prefix(即 WHERE);如果这里第一个条件是空的而第二个不是空,则去掉 prefixOverride(即这里的and)形成正确的sql
②自定义下面的set元素
<update id="xx">
update table1
<trim prefix="SET" suffixOverrides=",">
<if test="username != null and username != ''">username=#{username},</if>
<if test="sex != null and sex != ''">sex=#{sex},</if>
<if test="addr != null and addr != ''">address=#{addr},</if>
</trim>
where id = #{id}
</update>
(3)set
上面的where、trim可用来动态拼接条件,而set可用在update更新语句中动态包含要更新的列
<update id="xx">
update table1
<set>
<if test="username != null and username != ''">username=#{username},</if>
<if test="sex != null and sex != ''">sex=#{sex},</if>
<if test="addr != null and addr != ''">address=#{addr},</if>
</set>
where id = #{id}
</update>
11.4 foreach
如果要动态生成 如 select * from xx where field1 in (xxx,xxx,xxx) 这样的语句,调用者可传入数组、List、Set集合对象等
例:
<select id="findByAddrList" resultType="com.xt.domain.User">
select * from user where address in
<foreach item="addr" index="i" collection="list" open="(" separator="," close=")">
#{addr}
</foreach>
</select>
collection:传进来的参数名称
item:循环中当前元素,即值
index:集合下标或键
open、close:
separator:分隔符
另一个问题:如果输入的list或set、数组里面没有数据,则会导致 select * from t where address in () 这样的错误SQL
修改如下:
<select id="findByAddrList" resultType="com.xt.domain.User">
select * from user
<where>
<if test="list.size()>0">
address in
</if>
<foreach item="addr" index="i" collection="list" open="(" separator="," close=")">
#{addr}
</foreach>
</where>
</select>
11.5 bind
在OGNL表达式外创建一个变量,并将其绑定到当前的上下文
作用就是简化写法。而且bind是将param视作字符串拼接,能防止注入。
<select id="xx" resultType="xxx">
<bind name="pattern" value="'%' + param + '%'"/>
select * from table1 where name like #{pattern} --在上面定义的pattern在这里就用到了
</select>
12. xml映射文件中的标签
(1)sql标签
<select>、<insert>、<update>、<delete>
<selectKey>、<resultMap>、
(2)<sql>、<include>
<sql id="role_columns"> id,name,note </sql> <select id="xx" parameterType="xx" resultMap="xx"> select <include refid="role_columns"> from table1 </select>
13. 一对一、一对多、多对多关联查询
13.1 一对一
一对一:从实体类上说,就是类A有成员是B类对象,一个A类对象只有一个B类对象成员;
从表的关系上看,就是A表有一个字段是外键,关联到B表的主键,且通过A表记录的这个字段,只能从B表查出一条记录出来
(1)准备两张表
create table cards(cid int(5) primary key,cnum varchar(10)); create table students(sid int(5) primary key,sname varchar(10),scid int(5), constraint scid_fk foreign key(scid) references cards(cid)); insert into cards(10,'202010'); insert into cards(20,'202020'); insert into cards(30,'202030'); insert into students values(1,'name1',10); insert into students values(2,'name2',20); insert into students values(3,'name3',30);
(2)准备实体类
学生类:省略getter、setter
package com.xt.domain; public class Student { private int id; private String name; private Card card; //getter、setter }
身份证类:
package com.xt.domain; public class Card { private int id; private String num; }
(3)映射文件
CardDao.xml
<?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.xt.dao.CardDao"> <resultMap type="com.xt.domain.Card" id="cardMap"> <id property="id" column="cid"/> <result property="num" column="cnum"/> </resultMap> </mapper>
StudentDao.xml
<?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.xt.dao.StudentDao"> <resultMap type="com.xt.domain.Student" id="studentMap"> <id property="id" column="sid"/> <result property="name" column="sname"/> <association property="card" resultMap="com.xt.dao.CardDao.cardMap"/> </resultMap> <select id="findStudentBySid" resultMap="studentMap" parameterType="string"> select t1.sid,t1.sname,t2.cid,t2.cnum from students t1,cards t2 where t1.scid = t2.cid and t1.sid=#{sid} </select> </mapper>
(4)接口、测试使用
StudentDao.java CardDao.java暂无内容
Student findStudentBySid(String sid);
测试:
StudentDao studentDao = session.getMapper(StudentDao.class); //通过代理对象调用方法 Student student = studentDao.findStudentBySid("2"); System.out.println(student.getId()+" "+student.getName()+" "+student.getCard().getNum()); session.commit();
一对一的测试案例与下面的延迟加载案例非常类似,区别在于,延迟加载将这里的关联查询sql分成了两个sql,并使得实体类A的成员属性 B类对象可延迟加载
一对一案例说到底就是 <association>标签的使用
13.2 一对多
一对多:实体类A有一个属性成员,是B类对象的List或其它集合
表C有一个主键,表D有一个字段关联到表C的主键,且D中有很多记录的该字段值相同(则从表C的角度看过去,就是C的一条记录对应D的很多条记录了)
例子:班级信息表table1,有如下信息:班级编号、班级名字(根据一条班级编号就可以查询出很多条信息)
学生信息表,有如下信息:学号、姓名、班级编号
(1)准备表
CREATE TABLE grades(gid INT(5) PRIMARY KEY,gname VARCHAR(10)); CREATE TABLE studentss(sid INT(5) PRIMARY KEY,sname VARCHAR(10),sgid INT(5), CONSTRAINT sgid_fk FOREIGN KEY(sgid) REFERENCES grades(gid)); INSERT INTO grades VALUES(11,'高二'); INSERT INTO grades VALUES(10,'高一'); INSERT INTO studentss VALUES(1,'name1',11); INSERT INTO studentss VALUES(2,'name2',11); INSERT INTO studentss VALUES(3,'name3',11); INSERT INTO studentss VALUES(4,'name1',10); SELECT * FROM grades; SELECT * FROM studentss;
(2)实体类
班级类:
package com.xt.domain; import java.util.ArrayList; import java.util.List; public class Grade { private int id; private String name; private List<Studentt> studenttList = new ArrayList<Studentt>(); }
学生类:
package com.xt.domain; public class Studentt { private int id; private String name; private Grade grade; }
(3)映射文件
GradeDao.xml
<?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.xt.dao.GradeDao"> <resultMap type="com.xt.domain.Grade" id="gradeMap"> <id property="id" column="gid"/> <result property="name" column="gname"/> <collection property="studenttList" resultMap="com.xt.dao.StudenttDao.studenttMap"> <id property="id" column="sid"/> <result property="name" column="sname"/> </collection> </resultMap> <select id="findGradeByGName" parameterType="string" resultMap="gradeMap"> select t1.gid,t1.gname,t2.sid,t2.sname from grades t1,studentss t2 where t2.sgid=t1.gid and t1.gname=#{gname} </select> </mapper>
StudenttDao.xml
<?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.xt.dao.StudenttDao"> <resultMap type="com.xt.domain.Studentt" id="studenttMap"> <id property="id" column="sid"/> <result property="name" column="sname"/> </resultMap> </mapper>
(4)接口、测试
GradeDao.java
Grade findGradeByGName(String gname);
测试:
SqlSession session = factory.openSession(); GradeDao gradeDao = session.getMapper(GradeDao.class); //通过代理对象调用方法 Grade grade = gradeDao.findGradeByGName("高二"); System.out.println(grade.getId()+" "+grade.getName()+":"); List<Studentt> studenttList = grade.getStudenttList(); for(Studentt studentt:studenttList) { System.out.println(studentt.getId()+" "+studentt.getName()); } session.commit();
效果:根据班级名查到一个班级,而班级类对象里面有所有学生的信息
13.3 多对多
暂
13小结:
联合查询:联合多个表查询结果,在resultMap里面配置 association或collection 配置一对一或一对多的类存储数据
嵌套查询:先查一个表,然后根据这个表里的外键去查其父表(即分成几个sql来完成),查另外的表要通过select标签完成(例如下面的延迟加载就是嵌套查询的案例)
14. 延迟加载
延迟加载与按需加载
例:一张订单表,表里面有用户id字段,当执行查询订单时,可能需要根据用户id查询用户信息,也可能不需要。因此可对查询用户信息实行延迟加载。当没有获取用户信息的操作出现时,即不执行用户信息查询。
这里即将两个sql语句分开执行
14.1 设置
默认设置:
<settings> <setting name="lazyLoadingEnabled" value="false"/> <setting name="aggressiveLazyLoading" value="true"/> </settings>
lazyLoadingEnable:是否启用延迟加载
aggressiveLazyLoading:是否按需加载属性。为true时,当启用延迟加载时,只要加载对象,就加载对象的所有属性
真正需要的是按需加载:延迟加载开启(lazyLoadingEnabled为true),且按需加载开启(aggressiveLazyLoading为false)
若延迟加载关闭,则按需加载无论开启还是关闭,一开始所有sql都会执行
14.2 测试
14.2.1 准备两张表
//测试mybatis延迟加载
create table orders(id int primary key,
user_id int,
createtime timestamp default current_timestamp,
note varchar(20)
);
create table users(id int primary key auto_increment,
name varchar(20),
age int,
sex varchar(10),
money double
);
表orders有 user_id 字段,通过此字段可以去查询uses表获取user信息
14.2.2 实体类
Order.java(表orders有4个字段,但实体类有第五个属性,这第五个属性即是展现延迟加载的地方)
public class Order {
private int id;
private int userId;
private Date createTime;
private String note;
private User userInfo;
getter、setter
toString()
}
User.java
public class User {
private int id;
private String name;
private int age;
private String sex;
Double money;
getter、setter
}
延迟加载说明:当我们查询orders里面的信息,映射到Order实体类上时,如果不需要根据Order的 userInfo 字段去获取User信息,则不执行查询User的操作;当需要时才去执行
14.2.3 OrderDao、UserDao以及他们的映射文件
OrderDao.java
public interface OrderDao {
/*查询订单信息*/
List<Order> findAllOrders();
}
OrderDao.xml
<?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.xt.dao.OrderDao">
<!--配置查询所有-->
<select id="findAllOrders" resultMap="orderInfoResMap">
select * from orders
</select>
<resultMap type="com.xt.domain.Order" id="orderInfoResMap">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="createtime" property="createTime"/>
<result column="note" property="note"/>
<association property="userInfo" select="com.xt.dao.UserDao.findUserById" column="user_id"></association> <!--按需加载的地方-->
</resultMap>
</mapper>
UserDao.java
public interface UserDao {
/*根据订单id查询*/
User findUserById(int id);
}
UserDao.xml
<?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.xt.dao.UserDao">
<select id="findUserById" parameterType="int" resultType="com.xt.domain.User">
select * from users where id = #{id}
</select>
</mapper>
14.2.4 测试
(1)开启延迟加载,开启按需加载
<setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/>
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
OrderDao orderDao = session.getMapper(OrderDao.class);
//通过代理对象调用方法
List<Order> allOrders = orderDao.findAllOrders();
Order order = allOrders.get(0);
String note = order.getNote();
//String name = order.getUserInfo().getName();
session.close();
is.close();
测试结果:因为没有调用 getUserInfo(),则就不需要根据order里面的user_id 去查询用户信息了,mybatis日志输出发现只执行了查询orders表的sql
如果执行 getUserInfo,则会执行根据 user_id 查询 users 表的sql
(2)开启延迟加载,关闭按需加载
关闭了按需加载,则只要去获取Order的某个属性,所有的属性都会加载(包括本来要延迟加载的 userInfo 字段信息)
<setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="true"/>
测试代码同上,但当执行到 getNote() 时,第二个sql(根据 user_id 去查第二张表)也会执行
15. 缓存
(1)系统缓存
默认开启一级缓存,在参数和SQL完全一样的情况下,使用同一个SqlSession对象调用同一个Mapper的方法,一般只会执行一次SQL。但在一个SqlSession提交commit后再开一个SqlSession,如果也有缓存的话,它和之前的缓存是相互隔离的,需要重新执行SQL(即使参数、SQL还是一样)
二级缓存可使缓存在SqlSessionFactory层面上能提供给各个SqlSession对象共享
开启二级缓存要求返回的POJO能序列化(实现了Serializable接口),然后在映射xml文件里加上 <cache/>
①全局配置的settings里面加
<settings> <setting name="cacheEnabled" value="true" /> </settings>
②映射xml里面
<cache/>
也可以设置缓存的属性
如果某些语句需要每次调用都执行sql(即个别语句不用缓存),可在其标签内加 useCache flushCache属性
如:useCache默认值为true
<select id="xx" useCache="false" resultType="xxxxx" parameterType="xxx">
sql语句
</select>
(2)自定义缓存
比如用Redis作为缓存
需要实现 org.apache.ibatis.cache.Cache 接口,实现类在 cache标签里通过type属性指定
16. 自定义插件