三、MyBatis多表映射 3.1 多表映射概念
多表查询结果映射思路
上面课程中,我全面讲解了单表的mybatis操作!但是开发中更多的是多表查询 需求,这种情况我们如何让进行处理?
MyBatis 思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序查询需求,那就太好了,而 ResultMap 就是 MyBatis 就是完美答案。
官方例子:我们如何映射下面这个语句?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <select id ="selectBlogDetails" resultMap ="detailedBlogResultMap" > select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, A.id as author_id, A.username as author_username, A.password as author_password, A.email as author_email, A.bio as author_bio, A.favourite_section as author_favourite_section, P.id as post_id, P.blog_id as post_blog_id, P.author_id as post_author_id, P.created_on as post_created_on, P.section as post_section, P.subject as post_subject, P.draft as draft, P.body as post_body, C.id as comment_id, C.post_id as comment_post_id, C.name as comment_name, C.comment as comment_text, T.id as tag_id, T.name as tag_name from Blog B left outer join Author A on B.author_id = A.id left outer join Post P on B.id = P.blog_id left outer join Comment C on P.id = C.post_id left outer join Post_Tag PT on PT.post_id = P.id left outer join Tag T on PT.tag_id = T.id where B.id = #{id} </select >
你可能想把它映射到一个智能的对象模型,这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。 我们先来看看下面这个完整的例子,它是一个非常复杂的结果映射(假设作者,博客,博文,评论和标签都是类型别名)。 虽然它看起来令人望而生畏,但其实非常简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <!-- 非常复杂的结果映射 --> <resultMap id="detailedBlogResultMap" type="Blog" > <constructor> <idArg column="blog_id" javaType="int" /> </constructor> <result property="title" column="blog_title" /> <association property="author" javaType="Author" > <id property="id" column="author_id" /> <result property="username" column="author_username" /> <result property="password" column="author_password" /> <result property="email" column="author_email" /> <result property="bio" column="author_bio" /> <result property="favouriteSection" column="author_favourite_section" /> </association> <collection property="posts" ofType="Post" > <id property="id" column="post_id" /> <result property="subject" column="post_subject" /> <association property="author" javaType="Author" /> <collection property="comments" ofType="Comment" > <id property="id" column="comment_id" /> </collection> <collection property="tags" ofType="Tag" > <id property="id" column="tag_id" /> </collection> </collection> </resultMap>
你现在可能看不懂,接下来我们要学习将多表查询结果使用ResultMap标签映射到实体类对象上!
我们的学习目标:
多表查询语句使用
多表结果承接实体类设计
使用ResultMap完成多表结果映射
实体类设计方案
多表关系回顾:(双向查看)
- 一对一
夫妻关系,人和身份证号
- 一对多| 多对一
用户和用户的订单,锁和钥匙
- 多对多
老师和学生,部门和员工 实体类设计关系(查询):(单向查看)
- 对一 : 夫妻一方对应另一方,订单对应用户都是对一关系
实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Customer { private Integer customerId; private String customerName; } public class Order { private Integer orderId; private String orderName; private Customer customer; }
- 对多: 用户对应的订单,讲师对应的学生或者学生对应的讲师都是对多关系:
- 实体类设计:对多关系下,类中只要包含对方类型集合属性即可!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Customer { private Integer customerId; private String customerName; private List<Order> orderList; } public class Order { private Integer orderId; private String orderName; private Customer customer; }
多表结果实体类设计小技巧:
对一,属性中包含对方对象
对多,属性中包含对方对象集合
只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!
无论多少张表联查,实体类设计都是两两考虑!
在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合!
多表映射案例准备 数据库:
1 2 3 4 5 6 7 8 9 CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR (100 ), PRIMARY KEY (`customer_id`) );CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR (100 ), `customer_id` INT , PRIMARY KEY (`order_id`) ); INSERT INTO `t_customer` (`customer_name`) VALUES ('c01' );INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1' , '1' );INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2' , '1' );INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3' , '1' );
实际开发时,一般在开发过程中,不给数据库表设置外键约束。 原因是避免调试不方便。 一般是功能开发完成,再加外键约束检查是否有bug。
实体类设计:
稍后会进行订单关联客户查询,也会进行客户关联订单查询,所以在这先练习设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data public class Customer { private Integer customerId; private String customerName; private List<Order> orderList; } @Data public class Order { private Integer orderId; private String orderName; private Customer customer; }
3.2 对一映射
需求说明根据ID查询订单,以及订单关联的用户的信息!
OrderMapper接口
1 2 3 public interface OrderMapper { Order selectOrderWithCustomer (Integer orderId) ; }
OrderMapper.xml配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <resultMap id ="selectOrderWithCustomerResultMap" type ="order" > <id column ="order_id" property ="orderId" /> <result column ="order_name" property ="orderName" /> <association property ="customer" javaType ="customer" > <id column ="customer_id" property ="customerId" /> <result column ="customer_name" property ="customerName" /> </association > </resultMap > <select id ="selectOrderWithCustomer" resultMap ="selectOrderWithCustomerResultMap" > SELECT order_id,order_name,c.customer_id,customer_name FROM t_order o LEFT JOIN t_customer c ON o.customer_id=c.customer_id WHERE o.order_id=#{orderId} </select >
对应关系可以参考下图:
Mybatis全局注册Mapper文件
1 2 3 4 5 6 7 8 <mappers > <mapper resource ="mappers/OrderMapper.xml" /> </mappers >
junit测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Slf4j public class MyBatisTest { private SqlSession session; @BeforeEach public void init () throws IOException { session = new SqlSessionFactoryBuilder () .build( Resources.getResourceAsStream("mybatis-config.xml" )) .openSession(); } @Test public void testRelationshipToOne () { OrderMapper orderMapper = session.getMapper(OrderMapper.class); Order order = orderMapper.selectOrderWithCustomer(2 ); log.info("order = " + order); } @AfterEach public void clear () { session.commit(); session.close(); } }
关键词
在“对一”关联关系中,我们的配置比较多,但是关键词就只有:association 和javaType
3.3 对多映射
需求说明
查询客户和客户关联的订单信息!
CustomerMapper接口
1 2 3 4 5 public interface CustomerMapper { Customer selectCustomerWithOrderList (Integer customerId) ; }
CustomerMapper.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <resultMap id ="selectCustomerWithOrderListResultMap" type ="customer" > <id column ="customer_id" property ="customerId" /> <result column ="customer_name" property ="customerName" /> <collection property ="orderList" ofType ="order" > <id column ="order_id" property ="orderId" /> <result column ="order_name" property ="orderName" /> </collection > </resultMap > <select id ="selectCustomerWithOrderList" resultMap ="selectCustomerWithOrderListResultMap" > SELECT c.customer_id,c.customer_name,o.order_id,o.order_name FROM t_customer c LEFT JOIN t_order o ON c.customer_id=o.customer_id WHERE c.customer_id=#{customerId} </select >
对应关系可以参考下图:
Mybatis全局注册Mapper文件
1 2 3 4 5 6 7 <mappers > <mapper resource ="mappers/OrderMapper.xml" /> <mapper resource ="mappers/CustomerMapper.xml" /> </mappers >
junit测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testRelationshipToMulti () { CustomerMapper customerMapper = session.getMapper(CustomerMapper.class); Customer customer = customerMapper.selectCustomerWithOrderList(1 ); log.info("customer.getCustomerId() = " + customer.getCustomerId()); log.info("customer.getCustomerName() = " + customer.getCustomerName()); List<Order> orderList = customer.getOrderList(); for (Order order : orderList) { log.info("order = " + order); } }
关键词
在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:“collection”和“ofType”
3.4 多表映射总结 3.4.1 多表映射优化
setting属性
属性含义
可选值
默认值
autoMappingBehavior
指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。
NONE, PARTIAL, FULL
PARTIAL
我们可以将autoMappingBehavior设置为full,进行多表resultMap映射的时候,可以省略符合列和属性命名映射规则(列名=属性名,或者开启驼峰映射也可以自定映射)的result标签!
修改mybati-sconfig.xml:
1 2 <setting name ="autoMappingBehavior" value ="FULL" />
修改teacherMapper.xml
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="teacherMap" type ="teacher" > <id property ="tId" column ="t_id" /> <collection property ="students" ofType ="student" > <id property ="sId" column ="s_id" /> </collection > </resultMap >
3.4.2 多表映射总结
关联关系
配置项关键词
所在配置文件和具体位置
对一
association标签/javaType属性/property属性
Mapper配置文件中的resultMap标签内
对多
collection标签/ofType属性/property属性
Mapper配置文件中的resultMap标签内
四、MyBatis动态语句 4.1 动态语句需求和简介 经常遇到很多按照很多查询条件进行查询的情况,比如智联招聘的职位搜索等。其中经常出现很多条件不取值的情况,在后台应该如何完成最终的SQL语句呢?
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
4.2 if和where标签 使用动态 SQL 最常见情景是根据条件包含 where / if 子句的一部分。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <select id ="selectEmployeeByCondition" resultType ="employee" > select emp_id,emp_name,emp_salary from t_emp <where > <if test ="empName != null" > or emp_name=#{empName} </if > <if test ="empSalary > 2000" > or emp_salary>#{empSalary} </if > </where > </select >
4.3 set标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <update id ="updateEmployeeDynamic" > update t_emp <set > <if test ="empName != null" > emp_name=#{empName}, </if > <if test ="empSalary < 3000" > emp_salary=#{empSalary}, </if > </set > where emp_id=#{empId} </update >
4.4 trim标签(了解) 使用trim标签控制条件部分两端是否包含某些字符
prefix属性:指定要动态添加的前缀
suffix属性:指定要动态添加的后缀
prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <select id ="selectEmployeeByConditionByTrim" resultType ="com.atguigu.mybatis.entity.Employee" > select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp <trim prefix ="where" suffixOverrides ="and|or" > <if test ="empName != null" > emp_name=#{empName} and </if > <if test ="empSalary > 3000" > emp_salary>#{empSalary} and </if > <if test ="empAge < = 20" > emp_age=#{empAge} or </if > <if test ="empGender=='male'" > emp_gender=#{empGender} </if > </trim > </select >
4.5 choose/when/otherwise标签 在多个分支条件中,仅执行一个。
从上到下依次执行条件判断
遇到的第一个满足条件的分支会被采纳
被采纳分支后面的分支都将不被考虑
如果所有的when分支都不满足,那么就执行otherwise分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <select id ="selectEmployeeByConditionByChoose" resultType ="com.atguigu.mybatis.entity.Employee" > select emp_id,emp_name,emp_salary from t_emp where <choose > <when test ="empName != null" > emp_name=#{empName}</when > <when test ="empSalary < 3000" > emp_salary < 3000</when > <otherwise > 1=1</otherwise > </choose > </select >
4.6 foreach标签 基本用法
用批量插入举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <foreach collection ="empList" item ="emp" separator ="," open ="values" index ="myIndex" > (#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender}) </foreach >
批量更新时需要注意
上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:
1 atguigu.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true
对应的foreach标签如下:
1 2 3 4 5 6 7 8 <update id ="updateEmployeeBatch" > <foreach collection ="empList" item ="emp" separator =";" > update t_emp set emp_name=#{emp.empName} where emp_id=#{emp.empId} </foreach > </update >
关于foreach标签的collection属性
如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collection或list来引用这个list集合。这一点可以通过异常信息看出来:
1 Parameter 'empList' not found. Available parameters are [arg0, collection, list]
在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用@Param注解明确声明变量的名称,然后在foreach标签的collection属性中按照@Param注解指定的名称来引用传入的参数。
4.7 sql片段 抽取重复的SQL片段
1 2 3 4 5 <sql id ="mySelectSql" > select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp </sql >
引用已抽取的SQL片段
1 2 <include refid ="mySelectSql" />
五、MyBatis高级扩展 5.1 Mapper批量映射优化
需求
Mapper 配置文件很多时,在全局配置文件中一个一个注册太麻烦,希望有一个办法能够一劳永逸。
配置方式
Mybatis 允许在指定 Mapper 映射文件时,只指定其所在的包:
1 2 3 4 <mappers > <package name ="com.atguigu.mapper" /> </mappers >
此时这个包下的所有 Mapper 配置文件将被自动加载、注册,比较方便。
资源创建要求
Mapper 接口和 Mapper 配置文件名称一致
Mapper 接口:EmployeeMapper.java
Mapper 配置文件:EmployeeMapper.xml
Mapper 配置文件放在 Mapper 接口所在的包内
可以将mapperxml文件放在mapper接口所在的包!
可以在sources下创建mapper接口包一致的文件夹结构存放mapperxml文件
5.2 插件和分页插件PageHelper 5.2.1 插件机制和PageHelper插件介绍 MyBatis 对插件进行了标准化的设计,并提供了一套可扩展的插件机制。插件可以在用于语句执行过程中进行拦截,并允许通过自定义处理程序来拦截和修改 SQL 语句、映射语句的结果等。
具体来说,MyBatis 的插件机制包括以下三个组件:
Interceptor
(拦截器):定义一个拦截方法 intercept
,该方法在执行 SQL 语句、执行查询、查询结果的映射时会被调用。
Invocation
(调用):实际上是对被拦截的方法的封装,封装了 Object target
、Method method
和 Object[] args
这三个字段。
InterceptorChain
(拦截器链):对所有的拦截器进行管理,包括将所有的 Interceptor 链接成一条链,并在执行 SQL 语句时按顺序调用。
插件的开发非常简单,只需要实现 Interceptor 接口,并使用注解 @Intercepts
来标注需要拦截的对象和方法,然后在 MyBatis 的配置文件中添加插件即可。
PageHelper 是 MyBatis 中比较著名的分页插件,它提供了多种分页方式(例如 MySQL 和 Oracle 分页方式),支持多种数据库,并且使用非常简单。下面就介绍一下 PageHelper 的使用方式。
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md#如何配置数据库方言
5.2.2 PageHelper插件使用
pom.xml引入依赖
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 5.1.11</version > </dependency >
mybatis-config.xml配置分页插件
在 MyBatis 的配置文件中添加 PageHelper 的插件:
1 2 3 4 5 6 7 8 <plugins > <plugin interceptor ="com.github.pagehelper.PageInterceptor" > <property name ="helperDialect" value ="mysql" /> </plugin > </plugins >
其中,com.github.pagehelper.PageInterceptor
是 PageHelper 插件的名称,dialect
属性用于指定数据库类型(支持多种数据库)
页插件使用
在查询方法中使用分页:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Test public void testTeacherRelationshipToMulti () { TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class); PageHelper.startPage(1 ,2 ); List<Teacher> allTeachers = teacherMapper.findAllTeachers(); PageInfo<Teacher> pageInfo = new PageInfo <>(allTeachers); System.out.println("pageInfo = " + pageInfo); long total = pageInfo.getTotal(); System.out.println("total = " + total); int pages = pageInfo.getPages(); System.out.println("pages = " + pages); int pageNum = pageInfo.getPageNum(); System.out.println("pageNum = " + pageNum); int pageSize = pageInfo.getPageSize(); System.out.println("pageSize = " + pageSize); List<Teacher> teachers = pageInfo.getList(); System.out.println("teachers = " + teachers); teachers.forEach(System.out::println); }
5.3 逆向工程和MybatisX插件 5.3.1 ORM思维介绍 ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,最后我们就可以通过方法调用进行数据库操作!!
最终: 让我们可以使用面向对象思维进行数据库操作!!!
ORM 框架通常有半自动和全自动两种方式。
半自动 ORM 通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。
全自动 ORM 则是将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。
下面是半自动和全自动 ORM 框架的区别:
映射方式:半自动 ORM 框架需要程序员手动指定实体类和数据表之间的映射关系,通常使用 XML 文件或注解方式来指定;全自动 ORM 框架则可以自动进行实体类和数据表的映射,无需手动干预。
查询方式:半自动 ORM 框架通常需要程序员手动编写 SQL 语句并将查询结果集转换成实体对象;全自动 ORM 框架可以自动组装 SQL 语句、执行查询操作,并将查询结果转换成实体对象。
性能:由于半自动 ORM 框架需要手动编写 SQL 语句,因此程序员必须对 SQL 语句和数据库的底层知识有一定的了解,才能编写高效的 SQL 语句;而全自动 ORM 框架通过自动优化生成的 SQL 语句来提高性能,程序员无需进行优化。
学习成本:半自动 ORM 框架需要程序员手动编写 SQL 语句和映射配置,要求程序员具备较高的数据库和 SQL 知识;全自动 ORM 框架可以自动生成 SQL 语句和映射配置,程序员无需了解过多的数据库和 SQL 知识。
常见的半自动 ORM 框架包括 MyBatis 等;常见的全自动 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。
5.3.2 逆向工程 MyBatis 的逆向工程是一种自动化生成持久层代码和映射文件的工具,它可以根据数据库表结构和设置的参数生成对应的实体类、Mapper.xml 文件、Mapper 接口等代码文件,简化了开发者手动生成的过程。逆向工程使开发者可以快速地构建起 DAO 层,并快速上手进行业务开发。 MyBatis 的逆向工程有两种方式:通过 MyBatis Generator 插件实现和通过 Maven 插件实现。无论是哪种方式,逆向工程一般需要指定一些配置参数,例如数据库连接 URL、用户名、密码、要生成的表名、生成的文件路径等等。 总的来说,MyBatis 的逆向工程为程序员提供了一种方便快捷的方式,能够快速地生成持久层代码和映射文件,是半自动 ORM 思维像全自动发展的过程,提高程序员的开发效率。
注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!
5.3.3 逆向工程插件MyBatisX使用 MyBatisX 是一个 MyBatis 的代码生成插件,可以通过简单的配置和操作快速生成 MyBatis Mapper、pojo 类和 Mapper.xml 文件。下面是使用 MyBatisX 插件实现逆向工程的步骤:
安装插件:
在 IntelliJ IDEA 中打开插件市场,搜索 MyBatisX 并安装。
使用 IntelliJ IDEA连接数据库
查看生成结果
逆向工程案例使用
正常使用即可,自动生成单表的crud方法!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.atguigu.mapper;import com.atguigu.pojo.User;public interface UserMapper { int deleteByPrimaryKey (Long id) ; int insert (User record) ; int insertSelective (User record) ; User selectByPrimaryKey (Long id) ; int updateByPrimaryKeySelective (User record) ; int updateByPrimaryKey (User record) ; }
六、MyBatis总结
核心点
掌握目标
mybatis基础
使用流程, 参数输入,#{} ${},参数输出
mybatis多表
实体类设计,resultMap多表结果映射
mybatis动态语句
Mybatis动态语句概念, where , if , foreach标签
mybatis扩展
Mapper批量处理,分页插件,逆向工程