MyBatis授课笔记2

1 数据输出

2 基本数据类型

Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘com.atguigu.mapper.EmployeeMapper.selectEmpCount’. It’s likely that neither a Result Type nor a Result Map was specified.

int selectEmpCount();
<select id=”selectEmpCount” resultType=”int”>
select count(*) from t_emp
</select>

3 实体类型

public Employee findById(Integer empId);
<select id=”findEmp” resultType=”com.atguigu.entity.Employee”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_name like “%”#{ename}”%”
</select>
返回值是一个实体类型,resultType就写该类的完整路径,也可以使用alias别名

4 List类型

public List findAll();
返回值是一个List,resultType写List的泛型类型,也就是元素的类型

MyBatis授课笔记2 - 图1

5 Map类型

前提:返回值只有一条记录,可以多个字段。使用字段名(别名)做key,使用字段值做value
Expected one result (or null) to be returned by selectOne(), but found: 6

对于findById完全没有必要返回Map,直接返回Employee更合理

Map selectEmpNameAndMaxSalary();
<select id=”selectEmpNameAndMaxSalary” resultType=”map”>
SELECT
empname 员工姓名,
emp_salary 员工工资,
(SELECT _AVG
(empsalary) FROM t_emp) 部门平均工资
FROM t_emp WHERE emp_salary=(
SELECT _MAX
(emp_salary) FROM t_emp
)
</select>
MyBatis授课笔记2 - 图2

6 返回自增主键

购物车中有多件商品,完成下订单操作:
数据库表的设计:
订单主表,一个订单一条数据

Orderid 收货人 收货人地址 联系方式 总计
1 张三
2 李四

订单明细表

明细id 商品id 商品名称 商品单价 购买数量 所属订单的编号
1 Thinkpad 7000 10 1
2 Huawei 3000 20 1
3 Xiaomi 200 30 1

先添加订单主单的一条数据,会生成自增的主键。但是后面添加该订单多个明细时,需要用到刚刚生成的订单主单的主键。这就需要在添加完订单主单后立刻返回订单主键。

方法1:如果主键支持自增,可以直接采用该方式

<insert id=”saveEmp4” useGeneratedKeys=”true” keyProperty=”empId”>
insert into t_emp values (null,#{empName},#{empSalary})
</insert>
@Testpublic void testSaveEmp3(){
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = new Employee(null,“washington”,700.0);
System.out.println(emp.getEmpId());
int n = mapper.saveEmp4(emp);
System.out.println(n);
System.out.println(emp.getEmpId());
}
MyBatis授课笔记2 - 图3

方法2:不管主键是否支持自增,都可以采用该方式

<insert id=”saveEmp5”>
<selectKey order=”AFTER” keyProperty=”empId” resultType=”int”>
select @@identity
</selectKey>
insert into t_emp values (null,#{empName},#{empSalary})
</insert>

7 数据库表字段和实体类属性对应关系

8 自动映射

如果数据库字段和实体类的属性一致,会直接自动映射

<select id=”findById2” resultType=”map”>
select from t_emp where emp_id = ${empId}
</*select
>

9 手动映射(起别名)

如果数据库字段和实体类的属性不一致,就需要手动的映射,可以给每个列起一个别名,别名就是实体类的属性

<select id=”findById2” resultType=”map”>
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
where emp_id = ${empId}
</select>

10 手动映射(自动识别驼峰式命名规则)

如果数据库字段和实体类的属性不一致,就需要手动的映射,可以给每个列起一个别名,别名就是实体类的属性
缺点:每个SELECT语句中都要起别名
如果数据库表字段和实体类的属性不一致,但是有规律,比如字段是emp_id,对应的属性是empId。满足驼峰命名。只需要进行settings的设置即可。

MyBatis授课笔记2 - 图4
查看dtd,发现MyBatis配置文件的子元素是有顺序的。
MyBatis授课笔记2 - 图5
查看dtd,发现MyBatis的映射文件根元素mapper的子元素是没有顺序的
MyBatis授课笔记2 - 图6

<settings>
<setting name=”mapUnderscoreToCamelCase” value=”true”/>
</settings>
<select id=”findAll” resultType=”com.atguigu.entity.Employee”>
select _ _from t_emp
</*select
>

11 手动映射ResultMap

如果数据库表字段和实体类的属性不一致,但是没有规律,就可以写别名。但是繁琐,每个SELECT语句中都要写。可以使用ResultMap,定义一次,重复使用。
MyBatis授课笔记2 - 图7

12 多表查询应用场景

MyBatis授课笔记2 - 图8

在实际开发中,经常会将来自多张表的数据在一个位置显示。比如查询并显示的员工信息中会有来自部门表、岗位表的数据,而后台一般是定义一个方法: List findUser(conditions); 这就要求User中要包含部门Dept、岗位Position的信息;

MyBatis授课笔记2 - 图9

13 关联关系概念说明

14 关联类型1(数量)

①数量关系
主要体现在数据库表中

一对一:夫妻关系,人和身份证号

一对多:用户和用户的订单,锁和钥匙,一个班多个学生、一个部门多个员工

多对多:课程和学生 老师和学生

15 关联类型2(方向)

主要体现在Java实体类中

双向:双方都可以访问到对方
Customer:包含Order的集合属性
Order:包含单个Customer的属性

单向:双方中只有一方能够访问到对方
Customer:不包含Order的集合属性,访问不到Order
Order:包含单个Customer的属性

16 关联映射准备

17 创建数据库表

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_customer (customer_name) VALUES (‘c02’);
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’, ‘2’);

select from t_customer
select
from t_order

18 创建项目并创建实体类

| @Data
@AllArgsConstructor
@NoArgsConstructorpublic class Customer {
private Integer customerId;
private String customerName;

  1. **private **List<Order> **orderList **= **new **ArrayList<>();<br />} |

| —- | | @Data
@AllArgsConstructor
@NoArgsConstructorpublic class Order {
private Integer orderId;
private String orderName;
private Integer customerId;//知道所属客户的id,建议保留 insert
//private String customerName;
private Customer customer;//知道所属客户的所有信息 select
} |

注意:两个数据库表通过外键建立关联关系。两个实体类之间 通过属性(成员变量)来建立关联关系。

19 搭建项目框架

MyBatis授课笔记2 - 图10

20 (多、一)对一

查询某个订单的信息(携带客户的信息)

21 Mapper接口

public interface OrderMapper {
/
查询指定编号的订单,返回该订单信息(其中携带客户信息)
@param orderId
* @return
*/
Order selectOrderWithCustomer(Integer orderId);
}

22 Mapper映射文件

| <select id=”selectOrderWithCustomer” resultMap=”orderMap”>
select o.order_id,o.order_name,o.customer_id,c.customer_name
from t_order o
join t_customer c
on (o.customer_id = c.customer_id)
where order_id = #{orderId}
</select>

<resultMap id=”orderMap” type=”com.atguigu.entity.Order”>
<id column=”order_id” property=”orderId”></id>
<result column=”order_name” property=”orderName”></result>
<result column=”customer_id” property=”customerId”></result>

<association property=”customer” javaType=”com.atguigu.entity.Customer”>
<id column=”customer_id” property=”customerId”></id>
<result column=”customer_name” property=”customerName”></result>
</association>
</resultMap> | | —- |

23 测试类

@Testpublic void testSelectOrderWithCustomer(){
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.selectOrderWithCustomer(1);
System.out.println(order);
System.out.println(order.getCustomer());
}

24 测试

MyBatis授课笔记2 - 图11

MyBatis授课笔记2 - 图12

25 (多、一)对多

查询某个客户的信息(携带其多个订单信息)

26 Mapper接口

public interface CustomerMapper {
/
查询指定编号客户(携带订单信息,可能有多个订单)
@param customerId
* @return
*/
Customer selectCustomerWithOrderList(Integer customerId);
}

27 Mapper映射文件

| <select id=”selectCustomerWithOrderList” resultMap=”customerMap”>
select c.customer_id,c.customer_name,o.order_id,o.order_name
from t_customer c
join t_order o
on c.customer_id = o.customer_id
where c.customer_id=#{customerId}
</select>

<resultMap id=”customerMap” type=”com.atguigu.entity.Customer”>
<id column=”customer_id” property=”customerId”></id>
<result column=”customer_name” property=”customerName”></result>
<collection property=”orderList” ofType=”com.atguigu.entity.Order”>
<id column=”order_id” property=”orderId”></id>
<result column=”order_name” property=”orderName”></result>
</collection>
</resultMap> | | —- |

MyBatis授课笔记2 - 图13

28 测试类

@Testpublic void testSelectCustomerWithOrderList(){
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.selectCustomerWithOrderList(1);
System.out.println(customer);
List orderList = customer.getOrderList();
orderList.forEach(o-> System.out.println(o));
}

29 测试

总结:
多表连接查询的优点:

  1. 一条SQL语句查询多张表,结果包含多张表的数据
  2. 速度快

多表连接查询的缺点:

  1. 如果只想获取一张表的数据,但是多张表也会查询出来
  2. 对于一对多来说,这个问题更严重。只要客户名称,不要订单列表,却把订单列表(这是一个集合,多条记录)也查出来。对于多对一,这个问题还不太严重。要查询订单信息,同时把客户信息也查出来(就一个客户端,不是List)

  3. 那怎么办?

如果确定要使用多表的数据,直接使用多表连接查询
如果不确定要不要多表的数据,可以使用下面讲解的分步查询。

30 分步查询

如果使用连接查询(join),只有立即加载,没有延迟加载。数据不管是否需要,都会查询出来。
如果不确定是否需要,可以采用更加灵活的延迟加载。要使用延迟加载,必须将连接查询(一条SQL)变为分步查询(多条SQL语句)

示例:查询客户信息,可能要查看订单信息(查看订单信息时采取查询数据库获取订单信息),也可能不查看订单信息(那就不查询订单信息)

31 准备1:查询指定编号的客户(不带订单 单表查询)

public interface CustomerMapper {
Customer findById(Integer customerId);
}
<select id=”findById” resultType=”com.atguigu.entity.Customer”>
select _ _from t_customer where customer_id =#{customerId}
</*select
>
@Testpublic void testFindByCustomerId(){
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.findById(1);
System.out.println(customer);
}

32 准备2:查询指定编号的客户的订单(只有订单,单表)

public interface OrderMapper {
/
查询指定客户的订单,结果是List
@param customerId
* @return
*/
List findByCustomerId(Integer customerId);
}
<select id=”findByCustomerId” resultType=”com.atguigu.entity.Order”>
select _ _from t_order where customer_id=#{customerId}
</*select
>
@Testpublic void testFindOrderListByCustomerId(){
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List orderList = mapper.findByCustomerId(1);
orderList.forEach(order-> System.out.println(order));
}

33 实现以上两个单独操作的合并

<select id=”findById” resultMap=”customerMap2”>
select _ _from t_customer where customer_id =#{customerId}
</select>
<resultMap id=”customerMap2” type=”com.atguigu.entity.Customer”>
<id column=”customer_id” property=”customerId”></id>
<result column=”customer_name” property=”customerName”></result>
<collection property=”orderList”
select=”com.atguigu.mapper.OrderMapper.findByCustomerId”
column=”customer_id”
>
</collection>
</*resultMap
>

发现已经实现了两条SQL语句的自动查询,而不是1条了
MyBatis授课笔记2 - 图14

34 延迟加载

但是发现不管是否需要订单信息,哪怕只查询一个客户的编号,也会查询两次,获取订单信息。这个问题怎么解决??

分步查询是延迟加载的前提

35 什么是延迟加载

需要订单的时候,采取查询订单,不要订单信息,就不查询。

36 如何实现延迟加载

第一步:延迟加载(lazyload 懒加载)全局开关

<settings>

<setting name=”lazyLoadingEnabled” value=”true”/>
</settings>

MyBatis授课笔记2 - 图15

MyBatis授课笔记2 - 图16

第二步:分开关:fetchType

<resultMap id=”customerMap2” type=”com.atguigu.entity.Customer”>
<id column=”customer_id” property=”customerId”></id>
<result column=”customer_name” property=”customerName”></result>
<collection property=”orderList”
select=”com.atguigu.mapper.OrderMapper.findByCustomerId”
column=”customer_id”
fetchType=”eager”
>
</collection>
</resultMap>

MyBatis授课笔记2 - 图17

37 到底是立即加载还是延迟加载

连接(join)查询:一条SQL 语句,效率高。不灵活,只有立即加载,没有延迟加载
分步查询:多条SQL语句,效率低。灵活,可设置立即还是延迟(通过总开关和分开关)

如果确定多张表的数据都需要,直接使用连接查询
如果不确定多张表数据是否都需要,可能有些场景需要,有些场景不需要,可以使用分步查询并设置延迟加载(懒加载)

38 多对多关联和一对一关联

  1. 数据库表中,不管一对一、一对多、多对多都是通过外键来实现的
  2. 外键关系表示其实是一种一对多的关系

MyBatis授课笔记2 - 图18

  1. 多对多的关系如何使用外键搞定:引入一个中间表,将一个多对多变成两个一对多

中间表是多,之前的两个表是一
MyBatis授课笔记2 - 图19
在Java类中
方案1:可以象数据库表一样,定义三个实体类Course、Student、Inner,在Course、Inner之间建立一对多关系(Customer和Order),在Student和Inner之间建立一对多关系。
方案2:还可以在Java类中直接建立多对多关系
Course类 增加属性 List stuList = new ArrayList()
Student类 增加属性 List courseList = new ArrayList();
映射文件中均使用Collection进行映射。

public class Course {
private Integer cno;
private String cname;
private Integer credit;
private List studentList = new ArrayList<>();
}
public class Student {
private Integer sno;
private String sname;
private Integer age;
private List courseList = new ArrayList<>();
}
  1. 一对一的关系如何使用外键搞定

方案1:外键关联:将外键同时设为unique,将一对多变成了一对一
MyBatis授课笔记2 - 图20
方案2:主键关联:同时是主键和外键。主键保证了唯一和非空;外键保证了必须参考Person类的主键。不允许现有身份证号,再有人。
MyBatis授课笔记2 - 图21
在MyBatis中如何表示一对一;
Person类: IdCard idcard;
IdCard类:Person person;
两端的映射文件中都使用association。

39 动态SQL语句

40 动态SQL语句解决的问题

经常遇到很多按照很多查询条件进行查询的情况,比如智联招聘的职位搜索,比如OA系统中的支出查询等。其中经常出现很多条件不取值的情况,在后台应该如何完成最终的SQL语句呢?
MyBatis授课笔记2 - 图22
如果采用JDBC进行处理,需要根据条件是否取值进行SQL语句的拼接,一般情况下是使用StringBuilder类及其append方法实现,还是有些繁琐的。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

  • JDBC中如何处理多条件查询:字符串拼接,利于StringBuilder和append,需要编码
  • MyBatis中如何处理多条件查询:使用动态SQL,不需要编码,在xml中进行配置,利于修改

    41 if语句

    | <select id=”findEmp” resultType=”employee”>
    select _ _from t_emp where 1=1
    <if test=”empName!=null and empName!=’’”>
    and emp_name like “%”#{empName}”%”
    </if>
    <if test=”minSalary>0”>
    and emp_salary >= #{minSalary}
    </if>
    </*select
    > | | —- | | 技巧:增加一个条件 1=1 ,后面的条件都不是第一个条件,都以and开始 |

42 where语句

<select id=”findEmp” resultType=”employee”>
select _ _from t_emp
<where>
<if test=”empName!=null and empName!=’’”>
and emp_name like “%”#{empName}”%”
</if>
<if test=”minSalary>0”>
and emp_salary >= #{minSalary}
</if>
</where>
</*select
>
注意:where会自动的去掉第一个条件的and、or

43 trim语句

<select id=”findEmp” resultType=”employee”>
select _ _from t_emp
<trim prefix=”where” prefixOverrides=”and”>
<if test=”empName!=null and empName!=’’”>
and emp_name like “%”#{empName}”%”
</if>
<if test=”minSalary>0”>
and emp_salary >= #{minSalary}
</if>
</trim>
</*select
>
prefix属性:指定要动态添加的前缀
suffix属性:指定要动态添加的后缀
prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值

44 choose/when/otherwise标签(switch case)

<select id=”findEmp” resultType=”employee”>
select _ _from t_emp where
<choose>
<when test=”empName!=null and empName!=’’”>
emp_name like “%”#{empName}”%”
</when>
<when test=”minSalary>0”>
emp_salary >= #{minSalary}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</*select
>
不管有多少个条件满足,只执行第一个满足条件的
如果一个条件也没有,就执行otherwise,otherwise不要少