目录
1.2.2,创建log4j.properties配置文件详解 (不要改文件名字)
快速入门系列
https://blog.csdn.net/weixin_64972949/article/details/130945007?spm=1001.2014.3001.5502
概述
mybatis是什么?有什么特点?
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低
什么是ORM?
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系,比如用一个Java的Student类,去对应数据库中的一张student表,类中的属性和表中的列一一对应。Student类就对应student表,一个Student对象就对应student表中的一行数据
为什么mybatis是半自动的ORM框架?
用mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射关系,就可以直接进行CRUD操作了。由于mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以mybatis的数据库无关性低。虽然mybatis需要手写SQL,但相比JDBC,它提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发的效率。并且它的学习成本也比hibernate低很多
前置工作
首先,准备三张表,分别为emp3,t_customer,t_order。
emp3:
DROP TABLE IF EXISTS `emp3`;
CREATE TABLE `emp3` (
`emp_id` int(0) NOT NULL AUTO_INCREMENT,
`wkname` varchar(10) CHARACTER SET utf32 COLLATE utf32_general_ci NULL DEFAULT NULL,
`address` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'Unknown',
`job_id` int(0) NULL DEFAULT 0,
PRIMARY KEY (`emp_id`) USING BTREE,
INDEX `emp_name_index`(`wkname`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of emp3
-- ----------------------------
INSERT INTO `emp3` VALUES (1, '张三', '衡阳', 12);
INSERT INTO `emp3` VALUES (2, '李四', '郴州', 13);
INSERT INTO `emp3` VALUES (3, '王五', '岳阳', 14);
SET FOREIGN_KEY_CHECKS = 1;
t_order&t_customer
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');
一,引入log4j
1.1,为什么要引入log4j
首先为什么要引入log4j,先给大家看一个例子,在mybatis全局配置文件中,我将引入log4j的
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
先注释掉。
然后,我们再随便执行我们的一个测试方法 ,我们会发现,控制台打印了一堆我们看不懂的东西(这是因为我之前引入了SLF4J和logback日志显示,这两个东西有利于我们知道是哪里在报错,如果不引入,信息会更少)。
之后,我们在将注释取消,再执行同样的测试方法。我们会发现在控制台上有许多对我们有用的信息,最重要的就是它可以显示你当前执行的sql语句,和你传入的参数。这两个信息在我们调试方法的时候是非常有用的。所以我们引入log4j日志是十分有必要的。
1.2,如何引入log4j
1.2.1,导入log4j依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
1.2.2,创建log4j.properties配置文件详解 (不要改文件名字)
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/fanlan.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
1.2.3,在mybatis全局配置文件中引入
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
注意:在全局配置文件中配置信息的顺序是固定的,不可以随便改动。如下图,按照这张图的顺序去书写我们的配置信息。不然的话,就会报错。
大家可以参考一下,我的全局配置文件。注意:这里我导入了外部的jdbc.properties配置文件,至于properties配置文件我就不多讲了。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- Cause: org.apache.ibatis.builder.BuilderException: Error creating document instance.-->
<!-- Cause: org.xml.sax.SAXParseException; lineNumber: 58; columnNumber: 17; -->
<!-- 元素类型为 "configuration" 的内容必须匹配 "(properties?,settings?,typeAliases?,-->
<!-- typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,-->
<!-- environments?,databaseIdProvider?,mappers?)"。-->
<!-- 文件摆放的顺序是固定的,不可以改变-->
<!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
<environments default="development">
<!-- environment表示配置Mybatis的一个具体的环境,id:表示连接数据库环境的唯一标识,不可以重复 -->
<environment id="development">
<!-- Mybatis的内置的事务管理器 -->
<!-- type: JDBC/MANAGED-->
<!-- JDBC:表示当前环境中,执行SQL时,使用的是JDBC原生的事务管理方式,事务的提交和回滚是手动的-->
<!-- MANAGED:被管理,例如spring-->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- type:设置数据源的类型-->
<!-- type=POOLED|UNPOOLED|JNDI-->
<!-- POOLED:表示使用数据库连接池缓存数据库连接-->
<!-- UNPOOLED:不使用数据库连接池-->
<!-- JNDI:表示使用上下文中的数据源-->
<!-- 建立数据库连接的具体信息 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
<!-- mapper标签:配置一个具体的Mapper映射文件 -->
<!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
<!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
<mapper resource="mappers/EmpolyeeMapper.xml"/>
<!-- 在mapper标签的resource属性中指定Mapper配置文件以“类路径根目录”为基准的相对路径 -->
<mapper resource="mappers/OrderMapper.xml"/>
<mapper resource="mappers/CustomerMapper.xml"/>
</mappers>
</configuration>
工程目录结构如下图:
二,mybatis的输入输出
2.1,mybatis的SQL传参
2.1.1,#{}(使用的最多,有利于防止sql注入)
首先,调用查询方法,查询empId=1的员工。
对应的sql语句。
<select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
select emp_id empId,wkname name,address address,job_id jobId from emp3 where emp_id=#{empId}
</select>
可以看到,#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意。
2.1.2,${}(不安全)
将上面的sql语句改为。
<select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
select emp_id empId,wkname name,address address,job_id jobId from emp3 where emp_id=#{empId}
</select>
然后,在执行相同的方法,我们发现${}在底层为我们做的是字符串拼接 ,熟悉sql注入的就知道,字符串拼接就是一个大坑。
2.2,mybatis输入
2.2.1,单个参数
int deleteEmpolyeeById(Integer empId);
对应sql
<delete id="deleteEmpolyeeById">
delete from emp3 where emp_id=#{empId}
</delete>
当只有一个参数的时候,直接传就好,但是注意 :sql语句中写字段名,#{}中写对应的属性名
单个简单类型参数,在#{}中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。
2.2.2,实体类型参数
int updateEmpolyeeById(Empolyee empolyee);
对应sql
<update id="updateEmpolyeeById">
update emp3 set wkname=#{wkname},address=#{address}
where emp_id=#{empId}
</update>
Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。所以,使用实体类型数据的时候,#{}中的名字不可以自定义,不然无法通过getXxx()方法获取的值。
2.2.3,两个以上的简单类型的数据
int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
sql
<update id="updateEmployee">
update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
</update>
零散的多个简单类型参数,如果没有特殊处理,那么Mybatis无法识别自定义名称,所以我们需要使用@Param注解去标记。
2.2.4,Map类型的数据输入
int updateEmployeeByMap(Map<String, Object> paramMap);
sql
<update id="updateEmployeeByMap">
update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}
</update>
#{}写上Map中的Key值,就可以取出数据给到sql,语句中。
总结
1.对于单个简单类型的参数,在#{}中我们可以自定义他的名字,但是通常还是使用和接口方法参数同名。
2.对于实体类型参数,Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置
3.对于两个以上的简单的参数,我们需要使用@Param注解,不然mybatis无法识别
4.对于Map类型的数据,#{}中写Map中的key
2.3,mybatis输入
2.3.1,返回单个简单类型数据
int selectEmpCount();
sql
<select id="selectEmpCount" resultType="int">
select count(*) from t_emp
</select>
Mybatis 内部给常用的数据类型设定了很多别名。 以 int 类型为例,可以写的名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等。
2.3.2,返回实体类型
Empolyee selectEmpolyee(Integer empId);
sql
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmpolyee" resultType="com.zhuzhu.Empolyee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
select emp_id empId,wkname name,address address,job_id jobId
from emp3 where emp_id=${empId}
</select>
返回实体类型的时候,我们只需要将resultType设置成对应的实体类的全类名就好。
2.3.3,返回Map类型
Map<String,Object> selectEmpNameAndMaxJobId();
sql
<select id="selectEmpNameAndMaxJobId" resultType="map">
SELECT
wkname ,
job_id ,
(SELECT AVG(job_id) FROM emp3)
FROM emp3 WHERE job_id=(
SELECT MAX(job_id) FROM emp3
)
</select>
如下图,通过下面运行产生的日志文件,我们可以知道,返回的Map数据的Key值是字段名,而Value值就是查询出来的内容,比如说(”job_id“,"16")。
2.3.4,返回List数据
适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
List<Empolyee> selectAll();
sql
<select id="selectAll" resultType="com.zhuzhu.Empolyee">
select emp_id empId,wkname name,address address,job_id jobId from emp3
</select>
这里的resultType照样是写我们List集合中的元素的全类名
2.3.5,返回自增主键
通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢?有两种方式
1.使用useGeneratedKeys和keyProperty属性(要求数据库支持自增主键,比如说Mysql)
<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values(#{empName},#{empSalary})
</insert>
注意:Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。
2.使用子标签(用于不支持自增主键的数据库,比如说Oracle)
<insert id="insertEmployee" parameterType="com.atguigu.mybatis.beans.Employee" databaseId="oracle">
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select employee_seq.nextval from dual
</selectKey>
insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})
</insert>
2.3.6,数据库表字段和实体类属性对应关系
1.别名
将字段的别名设置成和实体类属性一致。
就比如我们在写select语句的时候,我们在字段名后面写上实体类的属性名。
<select id="selectAll" resultType="com.zhuzhu.Empolyee">
select emp_id empId,wkname name,address address,job_id jobId from emp3
</select>
2.全局配置自动识别驼峰式命名规则
在Mybatis全局配置文件加入如下配置:
注意:加的时候依然要注意写的顺序
<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
<!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
如果加了这个配置,那么你的select语句中就不用再去写别名了,但是前提是你的属性名和数据库中的字段名都需要符合命名规则:
比如说在数据库中字段名是emp_id,而属性名就是empId,其实就是将下划线后面的一个字母大写就好了。
3.使用resultMap
使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系
<resultMap id="selectEmployeeByRMResultMap" type="com.zhuzhu.Empolyee">
<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="emp_id" property="empId"/>
<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="wkname" property="wkname"/>
<result column="address" property="address"/>
</resultMap>
<!-- Employee selectEmployeeByRM(Integer empId); -->
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">
select emp_id,wkname,address from emp3 where emp_id=#{empId}
</select>
总结
1.返回简单类型的数据时,比如说int数据类型,你可以在resultType中写名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER。
2.返回实体类型的数据的时候,直接在resultType中写上实体类型的全类名。
3.返回Map类型的数据的时候,返回的Map数据的Key值是字段名,而Value值就是查询出来的内容,比如说(”job_id“,"16")。
4.返回List数据的时候,resultType照样是写我们List集合中的元素的全类名。
5.返回自增主键时,有两种方式,一种使用useGeneratedKeys和keyProperty属性适用于可自增主键的数据库——Mysql,还有一种使用selectKey标签适用于不可自增主键的数据库——Oracle。
6.数据库表字段和实体类属性对应关系,有三种方式——1.别名,2.自动匹配,3.使用resltMap进行绑定,只要实现其中的一种方式就可以了。
三,使用Mybatis映射关联关系
3.1,关联关系概念说明
3.1.1,数量关系
主要体现在数据库表中
一对一
夫妻关系,人和身份证号
一对多
用户和用户的订单,锁和钥匙
多对多
老师和学生,部门和员工
3.1.2,关联关系的方向
主要体现在Java实体类中
- 双向:双方都可以访问到对方
- Customer:包含Order的集合属性
- Order:包含单个Customer的属性
- 单向:双方中只有一方能够访问到对方
- Customer:不包含Order的集合属性,访问不到Order
- Order:包含单个Customer的属性
3.2,建模
3.2.1,order类
package com.zhuzhu;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;// 体现的是对一的关系
}
3.2.2,customer类
package com.zhuzhu;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
注意:表在前面我已经建好了。
3.2,对一
3.2.1,创建orderMapper接口
package com.zhuzhu.Dao;
import com.zhuzhu.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
Order selectOrderWithCustomer(Integer orderId);
}
3.2.2,创建orderMapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- mapper是根标签,namespace属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.zhuzhu.Dao.OrderMapper">
<!-- 创建resultMap实现“对一”关联关系映射 -->
<!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
<!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
<resultMap id="selectOrderWithCustomerResultMap" type="com.zhuzhu.Order">
<!-- 先设置Order自身属性和字段的对应关系 -->
<!-- 主键使用id标签,其他键使用result标签 -->
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<!-- 使用association标签配置“对一”关联关系 -->
<!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
<!-- javaType属性:一的一端类的全类名 -->
<association property="customer" javaType="com.zhuzhu.Customer">
<!-- 配置Customer类的属性和字段名之间的对应关系 -->
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>
<!-- Order selectOrderWithCustomer(Integer orderId); -->
<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>
</mapper>
在这里,我们使用 association标签去绑定order与customer之间的对一关系。
使用association标签配置“对一”关联关系
property属性:在Order类中对一的一端进行引用时使用的属性名
javaType属性:一的一端类的全类名
3.3,对多
3.3.1,创建customerMapper
package com.zhuzhu.Dao;
import com.zhuzhu.Customer;
public interface CustomerMapper {
Customer selectCustomerWithOrderList(Integer customerId);
}
3.3.2,创建customerMapper配置文件
<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap"
type="com.atguigu.mybatis.entity.Customer">
<!-- 映射Customer本身的属性 -->
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!-- collection标签:映射“对多”的关联关系 -->
<!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
<!-- ofType属性:集合属性中元素的类型 -->
<collection property="orderList" ofType="com.zhuzhu.Order">
<!-- 映射Order的属性 -->
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
</collection>
</resultMap>
<!-- Customer selectCustomerWithOrderList(Integer customerId); -->
<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>
collection标签:映射“对多”的关联关系
property属性:在Customer类中,关联“多”的一端的属性名
ofType属性:集合属性中元素的类型
3.4,分步查询
3.4.1,概念
为了实现延迟加载,对Customer和Order的查询必须分开,分成两步来做,才能够实现。为此,我们需要单独查询Order,也就是需要在Mapper配置文件中,单独编写查询Order集合数据的SQL语句。
3.4.2,编写sql文件
在customerMapper配置文件中实现分步查询,配置文件如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- mapper是根标签,namespace属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.zhuzhu.Dao.CustomerMapper">
<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap"
type="com.zhuzhu.Customer">
<!-- 映射Customer本身的属性 -->
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
<!-- 分步查询-->
<!-- orderList集合属性的映射关系,使用分步查询 -->
<!-- 在collection标签中使用select属性指定要引用的SQL语句 -->
<!-- select属性值的格式是:Mapper配置文件的名称空间.SQL语句id -->
<!-- column属性:指定Customer和Order之间建立关联关系时所依赖的字段 -->
<collection
property="orderList"
select="com.zhuzhu.Dao.CustomerMapper.selectOrderList"
column="customer_id"/>
</resultMap>
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
select customer_id,customer_name from t_customer
where customer_id=#{customerId}
</select>
<!-- 注意select语句要是没有resultmap去指定字段对应属性,
就需要在select语句中起别名去对应字段order_id orderId,order_name orderName-->
<select id="selectOrderList" resultType="com.zhuzhu.Order">
select order_id orderId,order_name orderName from t_order where customer_id=#{customer_id}
</select>
</mapper>
3.4.3,配置文件各元素之间的对应关系
3.5,延迟查询
3.5.1,概念
查询到Customer的时候,不一定会使用Order的List集合数据。如果Order的集合数据始终没有使用,那么这部分数据占用的内存就浪费了。对此,我们希望不一定会被用到的数据,能够在需要使用的时候再去查询。
例如:对Customer进行1000次查询中,其中只有15次会用到Order的集合数据,那么就在需要使用时才去查询能够大幅度节约内存空间。
延迟加载的概念:对于实体类关联的属性到需要使用时才查询。也叫懒加载。
3.5.2,较低版本
<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
<!-- 开启延迟加载功能:需要配置两个配置项 -->
<!-- 1、将lazyLoadingEnabled设置为true,开启懒加载功能 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 2、将aggressiveLazyLoading设置为false,关闭“积极的懒加载” -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
3.5.3,较高版本
<!-- Mybatis全局配置 -->
<settings>
<!-- 开启延迟加载功能 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
四,动态SQL
4.1,if和where
<select id="selectEmpolyeeByCondition" resultType="com.zhuzhu.Empolyee">
select emp_id empId,wkname wkname,address address,job_id jobId from emp3
<!-- where标签会自动去掉“标签体内前面多余的and/or” -->
<where>
<!-- 使用if标签,让我们可以有选择的加入SQL语句的片段。这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true -->
<!-- 在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段 -->
<if test="wkname!=null and wkname!=''">
and job_id=#{jobId}
</if>
<!-- <if test="jobId=null">-->
<!-- or address=#{address}-->
<!-- </if>-->
</where>
</select>
4.2,set
<update id="updateEmpolyeeDynamic">
update emp3
<set>
<if test="wkname!=null and wkname!=''">
<!-- 注意这里后面有个”,“逗号-->
wkname=#{wkname},
</if>
<!-- <if test="address=null">-->
<!-- address="未知",-->
<!-- </if>-->
</set>
where job_id=#{jobId}
</update>
4.3,trim
<select id="selectEmpolyeeByConditionByTrim" resultType="com.zhuzhu.Empolyee">
select emp_id empId,wkname wkname,address address,job_id jobId from emp3
<!-- prefix属性指定要动态添加的前缀 -->
<!-- suffix属性指定要动态添加的后缀 -->
<!-- prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值 -->
<!-- suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值 -->
<!-- 当前例子用where标签实现更简洁,但是trim标签更灵活,可以用在任何有需要的地方 -->
<trim prefix="where" suffixOverrides="and|or">
<if test="jobId > 10">
job_id>#{jobId} and
</if>
<!-- <if test="empAge <= 20">-->
<!-- emp_age=#{empAge} or-->
<!-- </if>-->
<!-- <if test="wkname!=null and wkname!=''">-->
<!-- wkname=#{wkname} or -->
<!-- </if>-->
</trim>
</select>
4.4,choose/when/otherwise
<select id="selectEmployeeByConditionByChoose" resultType="com.zhuzhu.Empolyee">
select emp_id empId,wkname wkname,address address,job_id jobId from emp3
where
<!-- - 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的when分支都不满足,那么就执行otherwise分支 -->
<choose>
<when test="wkname!=null and wkname!=''"> job_id=#{jobId}</when>
<otherwise>1=1</otherwise>
</choose>
</select>
4.5,foreach
collection属性:要遍历的集合
item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
index属性:这里起一个名字,便于后面引用
遍历List集合,这里能够得到List集合的索引值
遍历Map集合,这里能够得到Map集合的key
<insert id="insertEmployeeByConditionByForeach">
insert into emp3 value
<foreach collection="emps" item="emp" separator=",">
(#{emp.empId},#{emp.wkname},#{emp.address},#{emp.jobId})
</foreach>
</insert>
测试代码:
@Test
public void TestForeach(){
List<Empolyee> list = new ArrayList<>();
list.add(new Empolyee(10,"厦门","张三丰",15));
list.add(new Empolyee(11,"深圳","张无忌",16));
EmpolyeeMapper empolyeeMapper = session.getMapper(EmpolyeeMapper.class);
int i = empolyeeMapper.insertEmployeeByConditionByForeach(list);
}
可以看到,批量插入的本质其实是执行一条sql语句。
批量更新,实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:
url=jdbc:mysql://localhost:3306/h3?allowMultiQueries=true
五,缓存简介(由于是使用篇,这里就不多介绍了)
一级缓存
默认开启,同一个SqlSesion级别共享的缓存,在一个SqlSession的生命周期内,执行2次相同的SQL查询,则第二次SQL查询会直接取缓存的数据,而不走数据库,当然,若第一次和第二次相同的SQL查询之间,执行了DML(INSERT/UPDATE/DELETE),则一级缓存会被清空,第二次查询相同SQL仍然会走数据库
一级缓存在下面情况会被清除
在同一个SqlSession下执行增删改操作时(不必提交),会清除一级缓存
SqlSession提交或关闭时(关闭时会自动提交),会清除一级缓存
对mapper.xml中的某个CRUD标签,设置属性flushCache=true,这样会导致该MappedStatement的一级缓存,二级缓存都失效(一个CRUD标签在mybatis中会被封装成一个MappedStatement)
在全局配置文件中设置 <setting name="localCacheScope" value="STATEMENT"/>,这样会使一级缓存失效,二级缓存不受影响
二级缓存
默认关闭,可通过全局配置文件中的<settings name="cacheEnabled" value="true"/>开启二级缓存总开关,然后在某个具体的mapper.xml中增加<cache />,即开启了该mapper.xml的二级缓存。二级缓存是mapper级别的缓存,粒度比一级缓存大,多个SqlSession可以共享同一个mapper的二级缓存。注意开启二级缓存后,SqlSession需要提交,查询的数据才会被刷新到二级缓存当中
六,逆向工程
6.1,简介
正向:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向:先创建数据库表,由框架负责根据数据库表,反向生成实体类,mapper接口,mapper配置文件
6.2,配置pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>testMybatis</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>MybatisDemo2</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- Mybatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
6.3,MEG配置文件
在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>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/h3?serverTimezone=UTC&useSSL=false&rewriteBatchedStatements=true"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.zhuzhu.mybatis.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.zhuzhu.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="emp3" domainObjectName="Employee"/>
<!-- <table tableName="t_customer" domainObjectName="Customer"/>-->
<!-- <table tableName="t_order" domainObjectName="Order"/>-->
</context>
</generatorConfiguration>
<property name="enableSubPackages" value="true" />
在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false。
如下图:
6.4,执行MBG插件
配置好上述信息后,双击如下的插件,就可以逆向生成pojo,mapper接口,mapper.xml配置文件
6.5,QBC查询
6.5.1,版本选择
我们在配置文件的这个位置,指定是使用奢华版还是简洁版,版本的选择在生成pojo,mapper接口,mapper.xml配置文件之前就已经指定了,选择版本后才可以,生成逆向工程。
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3">
奢华版:查询方式更多,基本不需要再手写sql了。
简洁版:查询方式不多,有时候还需要自己手写sql。
6.5.2,QBC查询
在test包下,编写测试类testMBG类。
package com.zhuzhu;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhuzhu.mybatis.mapper.EmployeeMapper;
import com.zhuzhu.mybatis.pojo.Employee;
import com.zhuzhu.mybatis.pojo.EmployeeExample;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
public class TestMBG {
private SqlSession session;
private Logger log = LoggerFactory.getLogger(TestMBG.class);
// junit会在每一个@Test方法前执行@Before方法
@Before
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
//当openSession中的值是true的时候,就会自动提交事务
}
// junit会在每一个@Test方法后执行@After方法
@After
public void clear() {
//由于使用的是JDBC,所以不会自动提交事务,所以这里手动提交
session.commit();
session.close();
}
@Test
public void testMBG(){
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//条件查询
EmployeeExample employeeExample1 = new EmployeeExample();
employeeExample1.createCriteria().andAddressEqualTo("长沙");
employeeExample1.or().andJobIdBetween(12,14);
List<Employee> list = employeeMapper.selectByExample(employeeExample1);
list.forEach(employee -> System.out.println(employee));
//根据条件修改
//下面的意思就是说,将id为7的元素,修改为Employee(7,null,null,12)
EmployeeExample employeeExample2 = new EmployeeExample();
employeeExample2.createCriteria().andEmpIdEqualTo(7);
employeeMapper.updateByExampleSelective(new Employee(7,null,null,12),employeeExample);
}
}
EmployeeExample employeeExample1 = new EmployeeExample();
employeeExample1.createCriteria().andAddressEqualTo("长沙");
employeeExample1.or().andJobIdBetween(12,14);
由这里,我们可以知道,设置employeeExample就类似于编写我们的sql语句,就是条件。
6.6,分页插件
6.6.1,在pom文件中引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
6.6.2,使用
package com.zhuzhu;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zhuzhu.mybatis.mapper.EmployeeMapper;
import com.zhuzhu.mybatis.pojo.Employee;
import com.zhuzhu.mybatis.pojo.EmployeeExample;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
public class TestMBG {
private SqlSession session;
private Logger log = LoggerFactory.getLogger(TestMBG.class);
// junit会在每一个@Test方法前执行@Before方法
@Before
public void init() throws IOException {
session = new SqlSessionFactoryBuilder()
.build(
Resources.getResourceAsStream("mybatis-config.xml"))
.openSession();
//当openSession中的值是true的时候,就会自动提交事务
}
// junit会在每一个@Test方法后执行@After方法
@After
public void clear() {
//由于使用的是JDBC,所以不会自动提交事务,所以这里手动提交
session.commit();
session.close();
}
@Test
public void testMBG(){
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//查询所有数据
PageHelper.startPage(1,3);
List<Employee> list = employeeMapper.selectByExample(null);
list.forEach(employee -> System.out.println(employee));
PageInfo<Employee> pageInfo = new PageInfo<>(list);
System.out.println(pageInfo);
//获取总条数
System.out.println("总条数:"+pageInfo.getTotal()); //获取总页数
System.out.println("总页数"+pageInfo.getPages()); //获取当前页
System.out.println("当前页"+pageInfo.getPageNum()); //获取每页显示的条数
System.out.println("每页条数"+pageInfo.getSize());
}
}
最后
本篇博客最Mybatis使用的介绍就到这里了,如果本篇博客对你的有帮助的话,请点一个小赞支持一下,谢谢!咱们下篇博客再见!