MyBatis很重要,话不多,直接开始!!
八、MyBatis关联映射查询
关联查询是MyBatis一个很特殊的特性,MyBatis既可以使用SQL语句进行单表多表联查也可以做级联查询,而且效果比Hibernate显著的多,主要是因为在配置文件所标记实体类和数据表之间的关系非常明确.所以非常便于做关联映射查询,这样的缺点就是无法做级联增删改,不过我们的SQL语句完全可以独立完成这些,所以我们主要来说一下关联类型的级联查询即可.
两张表的表关系无非有这么三种:
一对一:(one to one)单纯的一对一关系映射关系有这四种:单向主键映射,单向外键映射,双向主键映射,双向外键映射,这里我们用的最多的,牵扯最少的,后期最便于维护的还是双向外键映射,那今天就以这个为例
一对多(多对一):(one to many),可以用一方主键关联多方外键.
多对多:(many to many),多对多这里涉及一个思想:所有的关联属于主外键联合关联,在下面我会详细解释这个问题的.
1.一对一(one to one)
关联类型 | 主键关联 | 外键关联 |
单向 | 单向主键关联 | 单向外键关联 |
双向 | 双向主键关联 | 双向外键关联 |
表设计:双向外键关联(两张表都需要添加外键)
案例:一个球队一个经理,一个经理执教一个球队
1>创建库表
#经理表()
CREATE TABLE manager(
mid int primary key,
mname varchar(20),
t_id int # 外键,代表经理执教一个球队
);
#球队表()
CREATE TABLE team(
tid int primary key,
tname varchar(20),
location varchar(20),
t_id int #外键,代表经理执教的球队
);
#插入数据
INSERT INTO manager values (1,'bobo',1),(2,'ker',2),(3,'suoluo',3),(4,'labo',3);
INSERT INTO team VALUES (1,'yongshi','圣安东尼奥',1),(2,'qishi','金州',2),(3,'maci','克利夫兰',3),(4,'maci',''克利夫兰,3);
#本人已经离开球坛四五余年,对于那支球队那位明星一点都不了解,希望不要见怪#
2>实体类设计
在双方实体类中持有表示双方的实体类属性;
manager:
--private int mid;
--private String mname;
// 在一方实体类中表示对方(一方)实体类的属性
--private TeamMapping team;
team:
--private int tid;
--private String tname;
--private String location;
// 在一方实体类中表示对方(一方)实体类的属性
--private ManagerMapping manager;
// 注意:这里省略了 空参构造方法,全参构造方法,getter and setter方法以及toString方法:注意在生成toString方法时去掉对方属性方法,否则在调用toString方法是会出现死循环
注意:在生成toString方法时去掉对方属性方法,否则在调用toString方法是会出现死循环
3>SQl映射文件
3.1>借助resultMap标签做关联映射
--property:当前实体类全限定名
--id:当前resultMap映射结果id
3.2>在resultMap标签中借助association标签来表示对方实体类属性名
--property:当前实体类中表示对方实体类属性名
--JavaType:对方实体类全限定名
3.3>在select查询标签中使用
resultType="映射结果id"来作为SQL语句的返回值类型(SQL语句当前表关系对方表外键)
<!-- ManagerMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<!-- SQL动态查询 -->
<mapper namespace="com.hekaikai666.dao.i.ManagerMapper">
<!-- 一对一的级联映射 -->
<!-- type:映射实体类全限定名 id:当前映射结果id -->
<resultMap type="com.hekaikai666.bean.Manager" id="managerBean">
<id property="mid" column="mid"/>
<result property="mname" column="mname"/>
<!-- 表示对方(一方)的实体类属性和表字段映射关系 -->
<!-- type:当前实体类中用于描述对方的实体类属性名 javaType:对方实体类全限定名 -->
<association property="team" javaType="com.hekaikai666.bean.Team">
<id property="tid" column="tid"/>
<result property="tname" column="tname"/>
<result property="location" column="location"/>
</association>
</resultMap>
<!-- 根据id查找一个Manager对象,以及他所关联的team对象 -->
<select id="findManagerById" parameterType="int" resultMap="managerBean">
SELECT manager.*,team.* FROM manager,team WHERE manager.mid = team.t_id AND manager.mid=#{mid}
</select>
</mapper>
<!-- TeamMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.hekaikai666.dao.i.TeamMapper">
<resultMap type="com.hekaikai666.bean.Team" id="teamBean">
<id property="tid" column="tid" />
<result property="tname" column="tname" />
<result property="location" column="location" />
<association property="manager" javaType="com.hekaikai666.bean.Manager">
<id property="mid" column="mid" />
<result property="mname" column="mname" />
</association>
</resultMap>
<select id="findTeamById" parameterType="int" resultMap="teamBean">
SELECT manager.*,team.* FROM manager,team WHERE team.tid =
manager.t_id AND team.tid=#{tid}
</select>
</mapper>
4>编写映射接口
/**
* 映射对象的接口
* @author hekaikai666
* @time 2018年9月18日下午5:14:34
**/
public interface TeamMapper {
public Team findTeamById(int id);
}
public interface ManagerMapper {
public Manager findManagerById(int id);
}
5>编写测试类测试接口
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.hekaikai666.bean.Emp;
import com.hekaikai666.bean.Manager;
import com.hekaikai666.bean.Team;
import com.hekaikai666.bean.User;
import com.hekaikai666.bean.User1;
import com.hekaikai666.dao.i.EmpMapper;
import com.hekaikai666.dao.i.ManagerMapper;
import com.hekaikai666.util.MyBatisUtil;
/**
*
* @author hekaikai666
* @time 2018年9月17日上午11:42:16
**/
public class TestManager {
SqlSessionFactory factory;
SqlSession session;
@Before
public void init() {
factory = MyBatisUtil.getFactory();
session = factory.openSession();
}
@After
public void destroy() {
session.commit();
session.close();
}
/**
* 关联映射查询管理员,获取管理员对象查询队伍
*/
@Test
public void test1() {
ManagerMapper mapper = session.getMapper(ManagerMapper.class);
Manager manager = mapper.findManagerById(2);
System.out.println(manager);
// 获取manager所关联的team对象
Team team = manager.getTeam();
System.out.println(team);
}
}
2.一对多,多对一(one to many,many to one)
表设计:在多方表建立外键来关联一方表主键
案例:一个用户有多张卡,多张卡对应一个用户
1>创建库表
#创建库表
create table people(
pid int primary key,
username varchar(20),
password varchar(20),
address varchar(100)
);
#卡表
create table card(
cid int primary key,
cno varchar(16),
remark varchar(20),
p_id int #外键 当前卡属于哪个外键
);
#插入数据
insert into people values(1,'张三','123456','雁塔区');
insert into people values(2,'李四','123456','碑林区');
insert into people values(3,'王五','123456','高新区');
insert into card values(1,'42001','工商银行',1);
insert into card values(2,'42002','招商银行',1);
insert into card values(3,'42003','建设银行',2);
insert into card values(4,'42004','人民银行',2);
2>实体类设计
在一方实体类定义表示多方实体类的属性
People:
--private int pid;
--private String username;
--private String address;
// 当前用户拥有多张卡
--private List<Card> cards;
在多方实体类定义表示一方实体类的属性
Card:
--private int cid;
--private String cno;
--private String remark;
// 表示多方实体类中的一方属性
--private People people;
// 注意:这里省略了 空参构造方法,全参构造方法,getter and setter方法以及toString方法:注意在生成toString方法时去掉对方属性方法,否则在调用toString方法是会出现死循环
3>映射文件
一方:在resultMap标签中定义collection
--property:当前实体类中定义表示多个对方的集合属性
--ofType 多方实体类全限定名
多方:在resultMap标签中定义association
--property:当前实体类中表示对方实体类属性名
--JavaType:对方实体类全限定名
<!-- PeopleMapper.xml配置 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.hekaikai666.dao.i.PeopleMapper">
<!-- 开启当前mapper下的二级缓存 -->
<cache/>
<!-- 开启二级缓存可选属性配置
eviction:缓存回收策略 → LRU:最近最少使用(默认);FIFO:先进先出;WEAK:弱引用;SOFT:软引用
flushInterval:缓存刷新时间 → 每隔多久刷新一次,不指定时执行查询时刷新缓存,单位是毫秒
readOnly:只读 → 默认为true
size:指定缓存中可以缓存多少个数据对象,超出这个指定数据,就可以指定回收策略来对数据进行回收,不指定默认是1024个 →
-->
<cache eviction="" flushInterval="" readOnly="" size=""></cache>
<!-- 借助resultMap表示一对多的级联映射关系 -->
<resultMap type="com.hekaikai666.bean.People" id="peopleBean">
<id property="pid" column="pid" />
<result property="username" column="username"/>
<result property="address" column="address"/>
<!-- 表示多方实体类属性和表字段之间的映射关系 -->
<!-- property:当前实体类中用于表示多方集合的属性名
ofType:多方实体类全限定名
-->
<collection property="cards" ofType="com.hekaikai666.bean.Card">
<id property="cid" column="cid"/>
<result property="cno" column="cno" />
<result property="remark" column="remark" />
</collection>
</resultMap>
<!-- 根据id查找people对象,以及他所关联的cards集合 -->
<select id="findPeopleById" parameterType="int" resultMap="peopleBean">SELECT people.*,card.* FROM people,card WHERE people.pid=card.p_id AND people.pid=#{pid}</select>
</mapper>
<!-- CardMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.hekaikai666.dao.i.CardMapper">
<!-- 借助resultMap表示多对一的级联映射关系 -->
<!-- 表示多方实体类属性和表字段之间的映射关系 -->
<!-- property:当前实体类中用于表示多方集合的属性名 ofType:多方实体类全限定名 -->
<resultMap type="com.hekaikai666.bean.Card" id="cardBean">
<id property="cid" column="cid" />
<result property="cno" column="cno" />
<result property="remark" column="remark" />
<!-- 表示一方实体类属性和表字段之间的映射关系 -->
<!-- property:当前实体类中用于表示对方集合的属性名 javaType:多方实体类全限定名 -->
<association property="people" javaType="com.hekaikai666.bean.People">
<id property="pid" column="pid" />
<result property="username" column="username" />
<result property="address" column="address" />
</association>
</resultMap>
<!-- 根据id查找card对象,以及他所关联的people集合 -->
<select id="findCardById" parameterType="int" resultMap="cardBean"><!-- SELECT
people.*,card.* FROM people,card WHERE people.pid=card.p_id AND
people.pid=#{pid} -->
SELECT people.*,card.* FROM people inner join card on people.pid=card.p_id WHERE card.cid=#{cid}
</select>
</mapper>
4>编写映射接口
/**
* 映射器接口类
* @author hekaikai666
* @time 2018年9月19日上午10:33:50
**/
public interface PeopleMapper {
public People findPeopleById(int id);
}
public interface CardMapper {
public Card findCardById(int id);
}
5>编写测试类
/**
*
* @author hekaikai666
* @time 2018年9月19日上午10:34:52
**/
public class TestPAC {
SqlSessionFactory factory;
SqlSession session;
@Before
public void init() {
factory = MyBatisUtil.getFactory();
session = factory.openSession();
}
@After
public void destroy() {
session.commit();
session.close();
}
/**
* 一对多的级联查询
*/
@Test
public void test1() {
// 通过session获取映射接口对象
PeopleMapper mapper = session.getMapper(PeopleMapper.class);
// 使用映射接口对象调用映射接口方法
People people = mapper.findPeopleById(2);
System.out.println(people);
// 使用获取到的对象调用级联查询映射的方法
List<Card> cards = people.getCards();
System.out.println(cards);
}
/**
* 一对多的级联查询
*/
@Test
public void test2() {
// 通过session获取映射接口对象
CardMapper mapper = session.getMapper(CardMapper.class);
// 使用映射接口对象调用映射接口方法
Card card = mapper.findCardById(2);
System.out.println(card);
// 使用获取到的对象调用级联查询映射的方法
People people = card.getPeople();
System.out.println(people);
}
}
3.多对多 many to many
表设计:双都是一对多,在双方表上都设置外键.
案例:一个老师对应多个学生,一个学生对应多个老师
思想:因为我们所建立的多对多映射表不含有第三张关系表(Hibernate中使用中间关系表),那我们应该怎样设立他们那的关系呢.第一张表的主键和第二张表的外键关联起来,这样就有一个一对多,同时,把第二张表的主键关联到第一张表的外键上,name两张表就有了多对多关联关系.第一假设有一个老师带数学,他名下有两个学生,在老师的表字段中加入一个学生标签,这样只能放一个,(1,2,3,...)这种不符合三大范式中的第一范式,所以这样是行不通的,但是我们的多对多级联映射查询时查询操作,对于表中的数据无法做任何操作,所以我们选用在学生的表的外键标记老师,如果需要修改老师,我们的操作是把之前的关系断开,再去连接新的关系,这样实行的是数据库的修改操作.与查询无关,所以我们可以采用这种方式 对数据库进行级联查询.
1>创建库表
#老师表
create table teacher(
tid int primary key,
tname varchar(20),
xueke varchar(10),
s_id int #外键,该老师属于那个学生
);
#学生表
create table student(
sid int primary key,
sname varchar(20),
sex char(1),
t_id int #外键,该学生属于那个老师.
);
#插入数据
insert into teacher values (1,'姜老师','语文',null);
insert into teacher values (2,'仓老师','数学',null);
insert into teacher values (3,'马老师','英语',null);
insert into teacher values (4,'波老师','物理',null);
insert into student values (1,'杨雪峰','男',null);
insert into student values (2,'尚万成','男',null);
insert into student values (3,'王鑫','男',null);
insert into student values (4,'孙传昊','男',null);
2>实体类设计
在双方实体类都有定义用于表示多个对方实体类集合属性.
Teacher
private int tid;
private String tname;
private String xueke;
// 表示当前Teacher关联多个Student
private List<Student> students;
Student
private int sid;
private String sname;
private String sex;
// 表示当前Student关联多个Teacher
private List<Teacher> teachers;
3>SQL映射文件
在resultMap标签中均定义Collection标签来表示对方(多方)的实体类和表字段之间的映射关系
--property:当前实体类中定义表示多个对方的属性集合
--ofType多方实体类全限定名
<!-- StudentMapper.xml配置文件 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.hekaikai666.dao.i.StudentMapper">
<!-- 借助resultMap表示多对多的级联映射 -->
<resultMap type="com.hekaikai666.bean.Student" id="studentBean">
<id property="sid" column="sid" />
<result property="sname" column="sname" />
<result property="sex" column="sex" />
<!-- 定义多方(对方)实体类属性和字段之间的映射关系 -->
<collection property="teachers" ofType="com.hekaikai666.bean.Teacher">
<id property="tid" column="tid" />
<result property="tname" column="tname" />
<result property="xueke" column="xueke" />
</collection>
</resultMap>
<!-- 根据ID查找当前Teacher对象以及它所关联的Student对象 -->
<select id="findStudentById" parameterType="int" resultMap="studentBean">select
teacher.*,student.* from teacher,student where
student.sid=teacher.s_id and student.sid=#{sid}
</select>
</mapper>
<!-- TeacherMapper.xml配置 -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
<mapper namespace="com.hekaikai666.dao.i.TeacherMapper">
<!-- 借助resultMap表示多对多的级联映射 -->
<resultMap type="com.hekaikai666.bean.Teacher" id="teacherBean">
<id property="tid" column="tid" />
<result property="tname" column="tname" />
<result property="xueke" column="xueke" />
<!-- 定义多方(对方)实体类属性和字段之间的映射关系 -->
<collection property="students" ofType="com.hekaikai666.bean.Student">
<id property="sid" column="sid" />
<result property="sname" column="sname" />
<result property="sex" column="sex" />
</collection>
</resultMap>
<!-- 根据ID查找当前Teacher对象以及它所关联的Student对象 -->
<select id="findTeacherById" parameterType="int" resultMap="teacherBean">select
teacher.*,student.* from teacher,student where
teacher.tid=student.t_id and teacher.tid=#{tid}</select>
</mapper>
4>映射接口
/**
* 映射接口对象
* @author hekaikai666
* @time 2018年9月19日下午3:26:51
**/
public interface TeacherMapper {
public Teacher findTeacherById(int id);
}
public interface StudentMapper {
public Student findStudentById(int id);
}
5>编写实体类测试接口
public class TestPAC {
SqlSessionFactory factory;
SqlSession session;
@Before
public void init() {
factory = MyBatisUtil.getFactory();
session = factory.openSession();
}
@After
public void destroy() {
session.commit();
session.close();
}
/**
* 多对多的级联查询
*/
@Test
public void test3() {
// 通过session获取映射接口的对象
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
// 使用接口的对象调用接口的方法
Teacher teacher = mapper.findTeacherById(4);
System.out.println(teacher);
// 使用获取到的对象调用吉利啊查询映射方法
List<Student> students = teacher.getStudents();
// 遍历获取到的数据
for (Student student : students) {
System.out.println("student:" + student);
}
}
/**
* 多对多的级联查询
*/
@Test
public void test4() {
// 通过session获取映射接口的对象
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 使用接口的对象调用接口的方法
Student student = mapper.findStudentById(3);
System.out.println(student);
// 使用获取到的对象调用吉利啊查询映射方法
List<Teacher> teachers = student.getTeachers();
// 遍历获取到的数据
for (Teacher teacher : teachers) {
System.out.println("teacher:" + teacher);
}
}
}
九、缓存(MyBatis和Hibernate缓存对比)
1.一级缓存
一级缓存指的是SQLSession缓存,在每一个SQLSession被创建出来的时候,内部都会有一个HashMap区域,也就是一级缓存区域,HashMap的Key是SQL语句,value值式SQL语句的查询结果,每次执行查询时,都会拿SQL语句去一级缓存HashMap中执行查询,如果查到了数据直接返回,没有查询就去数据库执行查询,将查询到的结果以key-value键值对的形式存储在HashMap中,那么第二次就可以直接来一级缓存中取出数据,从而提高查询性能,一级缓存在MyBatis中时默认开启的,只有当session执行了flush()或者close()之后,一级缓存才会被清空.
测试:
/**
* 测试一级缓存 相同的SqlSession执行了相同的sql语句
*/
@Test
public void testFirstLevelCache() {
PeopleMapper mapper = session.getMapper(PeopleMapper.class);
People people1 = mapper.findPeopleById(1);
session.close();
session = factory.openSession();
PeopleMapper mapper2 = session.getMapper(PeopleMapper.class);
People people2 = mapper2.findPeopleById(1);
System.out.println(people1);
System.out.println(people2);
2.二级缓存
二级缓存是SqlSessionFactory级别缓存,也是Mapper级别缓存,即同一个mapper下多个SqlSession可以共享二级缓存.二级缓存中也有一个HashMap区域,在开启二级缓存之后,第一次调用Sql语句,会去数据库执行查询,将查询到的数据以key-value键值对存储在对应的二级缓存区域,第二次如果还是同一个mapper下的SqlSession对象发送了相同的SQL语句,就回去二级缓存中执行查询取出数据.二级缓存总开关在MyBatis中也是默认开启的,如果哪一个mapper需要开启二级缓存,需要去对应的mapper映射文件中进行开启
使用步骤:
1>主配置文件中开启二级缓存总开关
主配置
<!-- 全局系统设置 -->
<settings>
<!-- 开启二级缓存..延迟加载之类的. -->
<!-- 打开二级总开关:默认是true开启的 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2>在需要开启二级缓存的mapper中对二级缓存进行开启
mapper.xml配置
<!-- 开启当前mapper下的二级缓存 -->
<cache/>
<!-- 开启二级缓存可选属性配置
eviction:缓存回收策略 → LRU:最近最少使用(默认);FIFO:先进先出;WEAK:弱引用;SOFT:软引用
flushInterval:缓存刷新时间 → 每隔多久刷新一次,不指定时执行查询时刷新缓存,单位是毫秒
readOnly:只读 → 默认为true
size:指定缓存中可以缓存多少个数据对象,超出这个指定数据,就可以指定回收策略来对数据进行回收,不指定默认是1024个 →
-->
<cache eviction="" flushInterval="" readOnly="" size=""></cache>
测试:
/**
* 测试二级缓存
* 同一个Mapper下不同SQLsession所共享的不同的SQLsession对同一个mapper下的SQL语句执行查询会共享二级缓存
*/
@Test
public void testSecondLevelCache() {
SqlSession session1 = factory.openSession();
PeopleMapper mapper1 = session1.getMapper(PeopleMapper.class);
People people1 = mapper1.findPeopleById(1);
System.out.println(people1);
session1.close();
SqlSession session2 = factory.openSession();
PeopleMapper mapper2 = session2.getMapper(PeopleMapper.class);
People people2 = mapper2.findPeopleById(1);
System.out.println(people2);
session2.close();
}