高级查询
Mybatis作为一个ORM框架,也对sql的高级查询作了支持,下面我来学习mybatis中的一对一,一对多, 多对多
案例说明
此案例中的业务关系是用户,订单,订单详情,商品之间的关系,其中
一个订单属性于一个人
一个订单可以有多个订单详情
一个订单详情中包含一个商品信息
它们的关系是:
订单和人是一对一关系
订单和订单详情是一对多的关系
订单和商品是多对多的关系
表分析
导入课程资料中的数据库及实体类
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iMxEzN0ATOlR2N2kTN2EGNzYzX0MzN0UTM1EzLcBTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
业务需求
一对一查询: 查询订单,并且查询出下单人信息。
一对多查询:查询订单,查询出下单人信息并且查询出订单详情。
多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
一对一查询
需求:查询订单,并且查询出下单人信息
sql分析
-- 查询订单,并且查询出下单人的信息
SELECT
*
FROM
tb_user as u
left join tb_order AS o ON u.id = o.user_id
WHERE
o.order_number = 20200921001
代码实现
@Data
public class Order {
private Integer id;
private Long userId;
private String orderNumber;
//添加User对象
private User user;
}
public interface UserMapper {
/**
* 一对一:查询订单,并且查询出下单人信息
* @param orderNumber
* @return
*/
public Order oneToOne(@Param("orderNumber") String orderNumber);
}
<!--一对一-->
<resultMap id="oneToOneResultMap" type="Order" autoMapping="true">
<!--order的id-->
<id property="id" column="id"/>
<!--一对一映射
association: 一对一映射
property: 表示子对象的属性名
javaType: 指定子对象的类型
autoMapping: 完成子对象属性自动映射
-->
<association property="user" javaType="User" autoMapping="true">
<!--user的id-->
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
</association>
</resultMap>
<!--一对一:查询订单,并且查询出下单人信息-->
<select id="oneToOne" resultMap="oneToOneResultMap">
SELECT * FROM
tb_user as u left join tb_order as o on u.id = o.user_id
WHERE
o.order_number = #{orderNumber}
</select>
public class UserDaoTest {
private UserMapper userMapper;
private SqlSession sqlSession;
SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception {
//指定核心配置文件的位置
String resource = "mybatis-config.xml";
//加载核心配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//构建sqlSessionFactory对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象,SqlSession可以操作crud
sqlSession = sqlSessionFactory.openSession();
//动态代理
userMapper = sqlSession.getMapper(UserMapper.class);
}
//一对一查询
@Test
public void oneToOne(){
Order order = this.userMapper.oneToOne("20200921001");
System.out.println(order);
}
}
一对多查询
**一对多查询:**查询订单,查询出下单人信息并且查询出订单详情
sql分析
-- 查询订单,查询出下单人信息并且查询出订单详情。
SELECT
o.id as o_id,
u.id as u_id,
d.id as d_id,
u.user_name,
o.order_number,
d.total_price
FROM
tb_order AS o
left join tb_user AS u ON o.user_id = u.id
left join tb_orderdetail as d on d.order_id = o.id
WHERE
o.order_number = 20200921001;
代码实现
/**
* 订单表
*/
@Data
public class Order {
private Integer id;
private Long userId;
private String orderNumber;
//一对一:添加User对象
private User user;
//一对多:添加Orderdetail
private List<Orderdetail> orderdetails;
}
/**
* 一对多:查询订单,查询出下单人信息并且查询出订单详情
* @param orderNumber
* @return
*/
public Order oneToMany(@Param("orderNumber") String orderNumber);
<!--一对多-->
<resultMap id="oneToManyResultMap" type="Order" autoMapping="true">
<!--映射order本身-->
<id property="id" column="o_id"/>
<!--映射user子对象-->
<association property="user" javaType="User" autoMapping="true">
<id property="id" column="u_id"/>
</association>
<!--映射Orderdetail-->
<collection property="orderdetails" javaType="List" ofType="Orderdetail" autoMapping="true">
<id property="id" column="d_id"/>
</collection>
</resultMap>
<!--一对多:查询订单,查询出下单人信息并且查询出订单详情-->
<select id="oneToMany" resultMap="oneToManyResultMap">
SELECT
o.id as o_id,
u.id as u_id,
d.id as d_id,
u.user_name,
o.order_number,
d.total_price
FROM
tb_order AS o
left join tb_user AS u ON o.user_id = u.id
left join tb_orderdetail as d on d.order_id = o.id
WHERE
o.order_number = #{orderNumber};
</select>
//一对多
@Test
public void oneToMany(){
Order order = this.userMapper.oneToMany("20200921001");
System.out.println(order);
}
多对多查询
**多对多查询:**查询订单,查询出下单人信息并且查询出订单详情中的商品数据
定单和商品表 是多对多的对应关系
sql分析
-- 查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
SELECT
o.id as o_id,
u.id as u_id,
d.id as d_id,
i.id as i_id,
u.user_name,
o.order_number,
d.total_price,
i.item_name
FROM
tb_order AS o
left join tb_user AS u ON o.user_id = u.id
left join tb_orderdetail as d on d.order_id = o.id
left join tb_item as i on d.item_id = i.id
WHERE
o.order_number = 20200921001
代码实现
@Data
public class Orderdetail {
private Integer id;
private Double totalPrice;
private Integer status;
/*商品信息*/
private Item item;
}
/**
* 多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。
* @param orderNumber
* @return
*/
public Order manyToMany(@Param("orderNumber") String orderNumber);
<!--多对多-->
<resultMap id="manyToManyResultMap" type="Order" autoMapping="true">
<!--映射order本身-->
<id property="id" column="o_id"/>
<!--映射user子对象-->
<association property="user" javaType="User" autoMapping="true">
<id property="id" column="u_id"/>
</association>
<!--映射Orderdetail-->
<collection property="orderdetails" javaType="List" ofType="Orderdetail" autoMapping="true">
<id property="id" column="d_id"/>
<!--映射item子对象-->
<association property="item" javaType="Item" autoMapping="true">
<id property="id" column="i_id"/>
</association>
</collection>
</resultMap>
<!--多对多查询:查询订单,查询出下单人信息并且查询出订单详情中的商品数据。-->
<select id="manyToMany" resultMap="manyToManyResultMap">
SELECT
o.id as o_id,
u.id as u_id,
d.id as d_id,
i.id as i_id,
u.user_name,
o.order_number,
d.total_price,
i.item_name
FROM
tb_order AS o
left join tb_user AS u ON o.user_id = u.id
left join tb_orderdetail as d on d.order_id = o.id
left join tb_item as i on d.item_id = i.id
WHERE
o.order_number = #{orderNumber}
</select>
//多对多
@Test
public void manyToMany(){
Order order = this.userMapper.manyToMany("20200921001");
System.out.println(order);
}
ResultMap的继承
回顾以上多表映射中resultMap映射中其实有 一对一,一对多,多对多中都有一对一对映射很重复的,每一次都需要写,不好,其实我们可以把相同的一对一映射进行抽取,然后再继承过来。
代码实现
分页插件
PageHelper分页插件
Mybatis的plugin实现原理
添加依赖
<!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.7.5</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>0.9.1</version>
</dependency>
配置插件
<!--分页插件-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!--数据库方言-->
<property name="dialect" value="mysql"/>
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true"/>
</plugin>
</plugins>
5.x后续方案
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<property name="reasonable" value="true"/>
</plugin>
实现分页
/**
* 分页查询
* @return
*/
public List<User> queryAll();
<!--分页查询-->
<select id="queryAll" resultType="User">
select * from tb_user
</select>
//分页查询
@Test
public void queryAll(){
//实现分页 参数1:当前页(从1开始) 参数2:显示多少条
PageHelper.startPage(2, 2);
//你只管查询所有,如何分页交给 PageHelper.startPage(2, 2);完成
List<User> users = this.userMapper.queryAll();
for (User user : users) {
System.out.println(user);
}
//获取更多的分页信息
PageInfo<User> pageInfo = new PageInfo<User>(users);
System.out.println("当前页:" + pageInfo.getPageNum());
System.out.println("当前页显示的条数:" + pageInfo.getPageSize());
System.out.println("总页码:" + pageInfo.getPages());
System.out.println("最后一页:" + pageInfo.getLastPage());
System.out.println("分页相关的信息:" + pageInfo.getList());
System.out.println("分页总条数:" + pageInfo.getTotal());
}
懒加载(延迟加载)
思考问题?
一对多:我们查询用户时,要不要把关联的订单查询出来
多对一:我们查询订单时,要不要把关联的用户查询出来
什么是懒加载?
就是在需要它的时候才加载,不需要的话就不加载
使用场景
一对多,多对多 通常采用延迟加载
一对一,多对一 通常采用立即加载
配置懒加载
<settings>
<!-- lazyLoadingEnabled:延迟加载启动,默认是false 相当于是否开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<!--aggressiveLazyLoading:积极的懒加载,falsed话按需加载,默认是true -->
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
一对多 collection延迟加载
一对多:我们查询用户时,要不要把关联的订单查询出来
@Data
public class User {
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private Integer sex;
private Date birthday;
private Date created;
private Date updated;
/*在一方添加多方对象*/
private List<Order> orders;
}
public List<User> queryAllUser(Integer id);
public Order queryOrderById(Integer id);
<resultMap id="userByIdMap" type="User" autoMapping="true">
<!--一对多,延迟加载-->
<collection property="orders" column="id" select="cn.yanqi.mapper.UserMapper.queryOrderById" ofType="Order" autoMapping="true"/>
</resultMap>
<select id="queryAllUser" resultMap="userByIdMap">
select * from tb_user where id = #{id}
</select>
<select id="queryOrderById" resultType="Order">
select * from tb_order where id = #{id}
</select>
@Test
public void queryUserById(){
List<User> list = this.userMapper.queryAllUser(2);
// list.forEach(System.out::println); 需要时才会加载order
}
多对一 association 延迟加载
多对一:我们查询订单时,要不要把关联的用户查询出来
@Data
public class Order {
private Integer id;
private Long userId;
private String orderNumber;
//一对一:添加User对象
private User user;
//一对多:添加Orderdetail
private List<Orderdetail> orderdetails;
}
public Order queryOrderById2(Integer id);
<resultMap id="OrderResultMap" type="Order" autoMapping="true">
<id column="id" property="id"/>
<!--多对一:延迟加载 -->
<association property="user" javaType="User" column="id" select="queryUserById" fetchType="lazy" autoMapping="true"/>
</resultMap>
<!--order查询-->
<select id="queryOrderById2" resultMap="OrderResultMap">
select * from tb_order where id = #{id}
</select>
<!--user查询-->
<select id="queryUserById" resultType="User">
select * from tb_user where id = #{id}
</select>
@Test
public void queryOrderById2(){
Order order = this.userMapper.queryOrderById2(2);
User user = order.getUser();//需要时才加载user
System.out.println(user);
}
逆向工程
我们之前都是根据数据库中表的字段来完成实体类的书写,再mapper接口和对应的sql映射文件,这此常规的操作是不是很重复,其实这些东西可以自动生成。以下就是告拆大家如何快速实现CRUD操作:
mybatis-generator
添加插件
- pom.xml
<build>
<plugins>
<!-- mybatis代码生成插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
配置文件
- datasource.properties
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql:///mybatis?characterEncoding=utf-8
db.username=root
db.password=root
- generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--导入属性配置-->
<properties resource="datasource.properties"></properties>
<!-- 指定数据库驱动的jdbc驱动jar包的位置 -->
<!--<classPathEntry location="${db.driverLocation}" />-->
<!-- context 是逆向工程的主要配置信息 -->
<!-- id:起个名字 -->
<!-- targetRuntime:设置生成的文件适用于那个 mybatis 版本 -->
<context id="default" targetRuntime="MyBatis3">
<!--optional,旨在创建class时,对注释进行控制-->
<commentGenerator>
<property name="suppressDate" value="true" />
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--jdbc的数据库连接-->
<jdbcConnection driverClass="${db.driverClassName}"
connectionURL="${db.url}"
userId="${db.username}"
password="${db.password}">
</jdbcConnection>
<!--非必须,类型处理器,在数据库类型和java类型之间的转换控制-->
<javaTypeResolver>
<!-- 默认情况下数据库中的 decimal,bigInt 在 Java 对应是 sql 下的 BigDecimal 类 -->
<!-- 不是 double 和 long 类型 -->
<!-- 使用常用的基本类型代替 sql 包下的引用类型 -->
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetPackage:生成的实体类所在的包 -->
<!-- targetProject:生成的实体类所在的硬盘位置 -->
<javaModelGenerator targetPackage="cn.yanqi.pojo"
targetProject=".\src\main\java">
<!-- 是否允许子包 -->
<property name="enableSubPackages" value="false" />
<!-- 是否对modal添加构造函数 -->
<property name="constructorBased" value="true" />
<!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
<property name="trimStrings" value="true" />
<!-- 建立modal对象是否不可改变 即生成的modal对象不会有setter方法,只有构造方法 -->
<property name="immutable" value="false" />
</javaModelGenerator>
<!-- targetPackage 和 targetProject:生成的 mapper 文件的包和位置 -->
<sqlMapGenerator targetPackage="mappers"
targetProject=".\src\main\resources">
<!-- 针对数据库的一个配置,是否把 schema 作为字包名 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage 和 targetProject:生成的 interface 文件的包和位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="cn.yanqi.dao" targetProject=".\src\main\java">
<!-- 针对 oracle 数据库的一个配置,是否把 schema 作为字包名 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<table tableName="tb_user" domainObjectName="User"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="tb_order" domainObjectName="Order"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="tb_orderdetail" domainObjectName="Orderdetail"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
<table tableName="tb_item" domainObjectName="Item"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
</table>
</context>
</generatorConfiguration>
IDEA插件代码生成器
Easy Code的代码生成器,从实体类到controller service dao sql都给生成好,很方便
IDEA连接数据库
下载插件
生成代码
false">