前面介绍了MyBatis的基本用法、关联映射和动态SQL等重要知识但这些知识只是针对单表实现进行操作的,在实际开发中,对数据库的操作常常会涉及到多张表,针对多表之间的操作MyBatis提供了关联映射,通过关联映射可以很好地处理表与表、对象与对象之间的关联关系。此外,在实际开发中经常需要合理地利用MyBatis缓存来加快数据库查询,进而有效地提升数据库性能。
关联映射关系:
在关系型数据库中,表与表之间存在着三种关联映射关系,分别为一对一关系、一对多关系和多对多关系。
1. 一对一:一个数据表中的一条记录最多可以和另一个数据表中的一条记录相关。例如,现实生活中学生与校园卡就属于一对一的关系,一个学生只能拥有一张校园卡,一张校园卡只能属于一个学生。
2.一对多: 主键数据表中的一条记录可以和另外一个数据表的多条记录相关。但另外一个数据表中的记录只能与主键数据表中的某一条记录相关。例如,现实中班级与学生的关系就属于一对多的关系,一个班级可以有很多学生,但一个学生只能属于一个班级。
3.多对多: 一个数据表中的一条记录可以与另外一个数据表任意数量的记录相关,另外一个数据表中的一条记录也可以与本数据表中任意数量的记录相关。例如,现实中学生与教师属于多对多的关系,一名学生可以由多名教师授课,一名教师可以为多名学生授课。
一对一
在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。
属性 | 说明 |
property | 用于指定映射到的实体类对象的属性,与表字段一一对应 |
column | 用于指定表中对应的字段 |
javaType | 用于指定映射到实体对象的属性的类型 |
jdbcType | 用于指定数据表中对应字段的类型 |
fetchType | 用于指定在关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy |
select | 用于指定引入嵌套查询的子SQL语句 |
autoMapping | 用于指定是否自动映射 |
typeHandler | 用于指定一个类型处理器 |
<association>元素是<resultMap>元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式
一对多
在MyBatis中,通过<collection>元素来处理一对多关联关系。<collection>元素的属性大部分与<association>元素相同,但其还包含一个特殊属性一ofType。ofType属性与javaType属性对应,它用于指定实体类对象中集合类属性所包含的元素的类型。 <collection>元素是<resultMap>元素的子元素,<collection >元素有嵌套查询和嵌套结果两种配置方式。
嵌套查询方式:
嵌套结果方式:
多对多
Mybatis一级缓存
举例
场景1:在一个SqlSession中,对数据表根据id进行连续两次查询,第一次查询时会执行sql语句,第二次查询时则会直接从缓存中读取数据,无需再次执行sql语句。
场景2:在一个SqlSession中,对数据表根据id进行两次查询,中间进行了一次update操作。这样,两次查询都会执行查询的sql语句。因为两次查询期间SqlSession执行了commit操作(执行新增/更新/删除),会清空SqlSession中的一级缓存,这样做的目的是为了让缓存中存储的是最新的信息
总结
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
Mybatis二级缓存
二级缓存是mapper(namespace)级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
总结
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
Mybatis注解开发
添加注解:在WorkerMapper接口中添加向tb_worker数据表插入数据的方法insertWorker(),并在方法上添加@Insert注解
在WorkerMapper接口中添加更新tb_worker表中数据的方法,并在方法上添加@Update注解
在WorkerMapper接口中添加删除数据库中数据的方法,并在方法上添加@Delete注解。
在WorkerMapper接口中添加多条件查询的方法。
package cn.hdc.dao; import cn.hdc.pojo.Worker; import org.apache.ibatis.annotations.*; public interface WorkerMapper { @Select("select * from tb_worker where id=#{id}") Worker selectWorker(int id); @Insert("insert into tb_worker(name,age,sex,worker_id) values(#{name},#{age},#{sex},#{worker_id})") int insertWorker(Worker worker); @Update("update tb_worker set name =#{name},age=#{age} where id=#{id}") int updateWorker(Worker worker); @Delete("delete from tb_worker where id = #{id}") int deleteWorker(int id); @Select("Select * from tb_worker where id=#{param01} and name=#{param02} ") Worker selectWorkerByIdAndName(@Param("param01") int id, @Param("param02") String name); }
编写测试测试类:
@Test public void insertWorkerTest(){ Worker worker=new Worker(); worker.setId(4); worker.setName("赵柳"); worker.setAge(36); worker.setSex("男"); worker.setWorker_id("1004"); WorkerMapper mapper=sqlSession.getMapper(WorkerMapper.class); int n =mapper.insertWorker(worker); if(n>0){ System.out.println("插入成功"+n+"条数据"); }else { System.out.println("插入失败"); } } @Test public void updateWorkerTest(){ Worker worker=new Worker(); worker.setId(4); worker.setName("tom"); worker.setAge(28); WorkerMapper mapper=sqlSession.getMapper(WorkerMapper.class); int n = mapper.updateWorker(worker); if (n>0){ System.out.println("成功修改"+n+"条数据"); }else { System.out.println("修改失败"); } } @Test public void deleteWorkerTest(){ WorkerMapper mapper=sqlSession.getMapper(WorkerMapper.class); int n=mapper.deleteWorker(4); if (n>0){ System.out.println("成功删除"+n+"条数据"); }else { System.out.println("删除 失败"); } } @Test public void selectWorkerByIdAndNameTest(){ WorkerMapper mapper=sqlSession.getMapper(WorkerMapper.class); Worker worker=mapper.selectWorkerByIdAndName(3,"王五"); System.out.print(worker); }
1.数据库准备 # 创建一个名称为c_class的表 CREATE TABLE c_class ( id INT(32) PRIMARY KEY AUTO_INCREMENT, classname VARCHAR(40) ); # 插入2条数据 INSERT INTO c_class VALUES (1, '一班'); INSERT INTO c_class VALUES (2, '二班'); # 创建一个名称为s_student的表 CREATE TABLE s_student ( id INT(32) PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(40), age INT, cid INT(32) NOT NULL, FOREIGN KEY(cid) REFERENCES c_class(id) ); # 插入4条数据 INSERT INTO s_student VALUES (1, '张三', 18,1); INSERT INTO s_student VALUES (2, '李四', 18,2); INSERT INTO s_student VALUES (3, '王五', 19,2); INSERT INTO s_student VALUES (4, '赵六', 20,1); 2.pojo类 IClass类 public class IClass { private Integer id; private String classname; private List<IStudent> studentList; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getClassname() { return classname; } public void setClassname(String classname) { this.classname = classname; } public List<IStudent> getStudentList() { return studentList; } public void setStudentList(List<IStudent> studentList) { this.studentList = studentList; } @Override public String toString() { return "IClass{" + "id=" + id + ", classname='" + classname + '\'' + ", studentList=" + studentList + '}'; } } IStudent类 public class IStudent { private Integer id; private String name; private int age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "IStudent{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
IStudentMapper public interface IStudentMapper { //根据id查询学生 @Select("select * from s_student where id=#{id}") IStudent selectStudent(int id); //修改学生信息 @Update("update s_student set name=#{name},age=#{age} where id=#{id}") int updateStudent(IStudent student); @Select("select * from s_student where cid=#{cid}") List<IStudent> selectStudentByCid(int cid); } IClass public interface IClassMapper { //根据id查询班级(并获取这个班级下的所有学生) @Select("select * from c_class where id=#{id}") @Results({@Result(id = true,column = "id",property = "id"), @Result(column = "classname",property = "classname"), @Result(column = "id",property = "studentList",many = @Many(select = "com.itheima.dao.IStudentMapper.selectStudentByCid")) }) IClass selectClassById(int id); }
//注解任务 @Test public void findIStudentByIdTest(){ IStudentMapper mapper=sqlSession.getMapper(IStudentMapper.class); IStudent student=mapper.selectStudent(2); System.out.println(student); } @Test public void updateIStudentTest(){ IStudentMapper mapper=sqlSession.getMapper(IStudentMapper.class); IStudent student=new IStudent(); student.setId(4); student.setName("李雷"); student.setAge(21); int result=mapper.updateStudent(student); if (result>0){ System.out.println("成功更新"+result+"条数据"); }else { System.out.println("更新数据失败!"); } } @Test public void selectClassByIdTest(){ IClassMapper mapper=sqlSession.getMapper(IClassMapper.class); IClass iClass=mapper.selectClassById(2); System.out.println(iClass); } |