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 |
---|
返回值是一个List,resultType写List的泛型类型,也就是元素的类型 |
5 Map类型
前提:返回值只有一条记录,可以多个字段。使用字段名(别名)做key,使用字段值做value
Expected one result (or null) to be returned by selectOne(), but found: 6
对于findById完全没有必要返回Map,直接返回Employee更合理
Map |
---|
<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> |
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()); } |
方法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的设置即可。
查看dtd,发现MyBatis配置文件的子元素是有顺序的。
查看dtd,发现MyBatis的映射文件根元素mapper的子元素是没有顺序的
<settings> <setting name=”mapUnderscoreToCamelCase” value=”true”/> </settings> |
---|
<select id=”findAll” resultType=”com.atguigu.entity.Employee”> select _ _from t_emp </*select> |
11 手动映射ResultMap
如果数据库表字段和实体类的属性不一致,但是没有规律,就可以写别名。但是繁琐,每个SELECT语句中都要写。可以使用ResultMap,定义一次,重复使用。
12 多表查询应用场景
在实际开发中,经常会将来自多张表的数据在一个位置显示。比如查询并显示的员工信息中会有来自部门表、岗位表的数据,而后台一般是定义一个方法: List
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;
**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 搭建项目框架
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 测试
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> |
| —- |
28 测试类
@Testpublic void testSelectCustomerWithOrderList(){ CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class); Customer customer = mapper.selectCustomerWithOrderList(1); System.out.println(customer); List orderList.forEach(o-> System.out.println(o)); } |
---|
29 测试
总结:
多表连接查询的优点:
- 一条SQL语句查询多张表,结果包含多张表的数据
- 速度快
多表连接查询的缺点:
- 如果只想获取一张表的数据,但是多张表也会查询出来
对于一对多来说,这个问题更严重。只要客户名称,不要订单列表,却把订单列表(这是一个集合,多条记录)也查出来。对于多对一,这个问题还不太严重。要查询订单信息,同时把客户信息也查出来(就一个客户端,不是List)
那怎么办?
如果确定要使用多表的数据,直接使用多表连接查询
如果不确定要不要多表的数据,可以使用下面讲解的分步查询。
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 } |
---|
<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.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> |
---|
34 延迟加载
但是发现不管是否需要订单信息,哪怕只查询一个客户的编号,也会查询两次,获取订单信息。这个问题怎么解决??
35 什么是延迟加载
36 如何实现延迟加载
第一步:延迟加载(lazyload 懒加载)全局开关
<settings> <setting name=”lazyLoadingEnabled” value=”true”/> </settings> |
---|
第二步:分开关: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> |
---|
37 到底是立即加载还是延迟加载
连接(join)查询:一条SQL 语句,效率高。不灵活,只有立即加载,没有延迟加载
分步查询:多条SQL语句,效率低。灵活,可设置立即还是延迟(通过总开关和分开关)
如果确定多张表的数据都需要,直接使用连接查询
如果不确定多张表数据是否都需要,可能有些场景需要,有些场景不需要,可以使用分步查询并设置延迟加载(懒加载)
38 多对多关联和一对一关联
- 数据库表中,不管一对一、一对多、多对多都是通过外键来实现的
- 外键关系表示其实是一种一对多的关系
- 多对多的关系如何使用外键搞定:引入一个中间表,将一个多对多变成两个一对多
中间表是多,之前的两个表是一
在Java类中
方案1:可以象数据库表一样,定义三个实体类Course、Student、Inner,在Course、Inner之间建立一对多关系(Customer和Order),在Student和Inner之间建立一对多关系。
方案2:还可以在Java类中直接建立多对多关系
Course类 增加属性 List
Student类 增加属性 List
映射文件中均使用Collection进行映射。
public class Course { private Integer cno; private String cname; private Integer credit; private List } |
---|
public class Student { private Integer sno; private String sname; private Integer age; private List } |
- 一对一的关系如何使用外键搞定
方案1:外键关联:将外键同时设为unique,将一对多变成了一对一
方案2:主键关联:同时是主键和外键。主键保证了唯一和非空;外键保证了必须参考Person类的主键。不允许现有身份证号,再有人。
在MyBatis中如何表示一对一;
Person类: IdCard idcard;
IdCard类:Person person;
两端的映射文件中都使用association。
39 动态SQL语句
40 动态SQL语句解决的问题
经常遇到很多按照很多查询条件进行查询的情况,比如智联招聘的职位搜索,比如OA系统中的支出查询等。其中经常出现很多条件不取值的情况,在后台应该如何完成最终的SQL语句呢?
如果采用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不要少 |