【MyBatis】(三)MyBatis的SQL操作(MyBatis简单特性关联映射查询,缓存机制,MyBatis和Hibernate缓存对比)
MyBatis很重要,话不多,直接开始!!
八、MyBatis关联映射查询
关联查询是MyBatis一个很特殊的特性,MyBatis既可以使用SQL语句进行单表多表联查也可以做级联查询,而且效果比Hibernate显著的多,主要是因为在配置文件所标记实体类和数据表之间的关系非常明确.所以非常便于做关联映射查询,这样的缺点就是无法做级联增删改,不过我们的SQL语句完全可以独立完成这些,所以我们主要来说一下关联类型的级联查询即可.
两张表的表关系无非有这么三种:
一对一:(one to one)单纯的一对一关系映射关系有这四种:单向主键映射,单向外键映射,双向主键映射,双向外键映射,这里我们用的最多的,牵扯最少的,后期最便于维护的还是双向外键映射,那今天就以这个为例
一对多(多对一):(one to many),可以用一方主键关联多方外键.
多对多:(many to many),多对多这里涉及一个思想:所有的关联属于主外键联合关联,在下面我会详细解释这个问题的.
1.一对一(one to one)
关联类型 | 主键关联 | 外键关联 |
单向 | 单向主键关联 | 单向外键关联 |
双向 | 双向主键关联 | 双向外键关联 |
表设计:双向外键关联(两张表都需要添加外键)
案例:一个球队一个经理,一个经理执教一个球队
1>创建库表
[code]#经理表() 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>实体类设计
在双方实体类中持有表示双方的实体类属性;
[code]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语句当前表关系对方表外键)
[code]<!-- 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>
[code]<!-- 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>编写映射接口
[code]/** * 映射对象的接口 * @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>编写测试类测试接口
[code]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>创建库表
[code]#创建库表 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>实体类设计
在一方实体类定义表示多方实体类的属性
[code]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:对方实体类全限定名
[code]<!-- 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>
[code]<!-- 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>编写映射接口
[code]/** * 映射器接口类 * @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>编写测试类
[code]/** * * @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>创建库表
[code]#老师表 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>实体类设计
在双方实体类都有定义用于表示多个对方实体类集合属性.
[code]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多方实体类全限定名
[code]<!-- 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>
[code]<!-- 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>映射接口
[code]/** * 映射接口对象 * @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>编写实体类测试接口
[code]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()之后,一级缓存才会被清空.
测试:
[code]/** * 测试一级缓存 相同的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>主配置文件中开启二级缓存总开关
主配置
[code]<!-- 全局系统设置 --> <settings> <!-- 开启二级缓存..延迟加载之类的. --> <!-- 打开二级总开关:默认是true开启的 --> <setting name="cacheEnabled" value="true"/> </settings>
2>在需要开启二级缓存的mapper中对二级缓存进行开启
mapper.xml配置
[code]<!-- 开启当前mapper下的二级缓存 --> <cache/> <!-- 开启二级缓存可选属性配置 eviction:缓存回收策略 → LRU:最近最少使用(默认);FIFO:先进先出;WEAK:弱引用;SOFT:软引用 flushInterval:缓存刷新时间 → 每隔多久刷新一次,不指定时执行查询时刷新缓存,单位是毫秒 readOnly:只读 → 默认为true size:指定缓存中可以缓存多少个数据对象,超出这个指定数据,就可以指定回收策略来对数据进行回收,不指定默认是1024个 → --> <cache eviction="" flushInterval="" readOnly="" size=""></cache>
测试:
[code] /** * 测试二级缓存 * 同一个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(); }阅读更多
- 全面解析Hibernate关联操作、查询操作、高级特性、并发处理机制
- Hibernate关联操作、查询操作、高级特性、并发处理机制
- Mybatis实例 简单查询 事务处理 关联、集合查询 鉴别器 动态SQL及各种标签实例
- java的orm框架 mybatis 多对多 一对多关系的关联映射和查询--简单易懂,理解才是王道
- MyBatis高级映射和查询缓存
- 关于如何减缓、解除hibernate与domain之间的强关系限制以及与mybatis简单对比
- Hibernate 缓存机制续 - 查询缓存
- (SqlSessionTemplate和SessionFactory)sqlsession的产生过程,hibernate和mybatis的对比
- Mybatis框架运行机制(增删改查,一对一,一对多,日志系统,单元测试,版本控制,缓存,动态Sql)
- Hibernate与MyBatis的缓存机制
- hibernate将sql或hql查询结果集映射为实体类 不需要映射文件
- Ehcache二级缓存使用和不使用,iterate()方法查询的sql语句对比
- MyBatis学习 之 二、SQL语句映射文件(2)增删改查、参数、缓存
- JAVAWEB开发之mybatis详解(二)——高级映射、查询缓存、mybatis与Spring整合以及懒加载的配置和逆向工程
- MyBatis学习(一)之一对一关联映射查询
- Hibernate SQLQuery执行实体查询带来的多个无关联查询语句
- hibernate的Session操作, 查询过滤, 缓存利用, 批量处理
- Hibernate的HQL查询语句对比Sql语句学习
- Hibernate中一对多关联映射时,查询某个对象报 java.lang.StackOverflowError错误