快速入门 准备数据模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 CREATE DATABASE `mybatis-example`; USE `mybatis-example`; CREATE TABLE `t_emp`( emp_id INT AUTO_INCREMENT, emp_name CHAR(100), emp_salary DOUBLE(10,5), PRIMARY KEY(emp_id) ); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependencies> <!-- mybatis依赖 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.11</version> </dependency> <!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!--junit5测试--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.3.1</version> </dependency> </dependencies>
编写实体类
1 2 3 4 5 6 7 8 9 10 public class Employee { private Integer empId; private String empName; private Double empSalary; //getter | setter }
定义 mapper 接口****
1 2 3 4 5 6 7 package com.cgdcgd.cc.mapper; import com.cgdcgd.cc.demo.Employee; public interface EmployeeMapper { Employee getEmployeeById(int id); }
定义 mapper.xml**
位置: resources/mappers/EmployeeMapper.xml
方法名和 SQL 的 id 一致
方法返回值和 resultType 一致
方法的参数和 SQL 的参数一致
接口的全类名和映射配置文件的名称空间一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace等于mapper接口类的全限定名,这样实现对应 --> <mapper namespace="com.cgdcgd.cc.mapper.EmployeeMapper"> <!-- 查询使用 select标签 id = 方法名 resultType = 返回值类型 标签内编写SQL语句 --> <select id="getEmployeeById" resultType="com.cgdcgd.cc.demo.Employee"> <!-- #{key} : 占位符 + 赋值 emp_id = ? ? = id ${key} : 字符串拼接 "emp_id = " + id 推荐使用 #{key} 防止sql注入 当容器名、列名为动态时 使用${key} --> select emp_id empId,emp_name empName, emp_salary empSalary from t_emp where emp_id = #{empId} </select> </mapper>
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?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> <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --> <environments default="development"> <!-- environment表示配置Mybatis的一个具体的环境 --> <environment id="development"> <!-- Mybatis的内置的事务管理器 --> <transactionManager type="JDBC"/> <!-- 配置数据源 --> <dataSource type="POOLED"> <!-- 建立数据库连接的具体信息 --> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/> <property name="username" value="root"/> <property name="password" value="xxbsxy"/> </dataSource> </environment> </environments> <mappers> <!-- Mapper注册:指定Mybatis映射文件的具体位置 --> <!-- mapper标签:配置一个具体的Mapper映射文件 --> <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --> <!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --> <mapper resource="mappers/EmployeeMapper.xml"/> </mappers> </configuration><?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> <!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --> <environments default="development"> <!-- environment表示配置Mybatis的一个具体的环境 --> <environment id="development"> <!-- Mybatis的内置的事务管理器 --> <transactionManager type="JDBC"/> <!-- 配置数据源 --> <dataSource type="POOLED"> <!-- 建立数据库连接的具体信息 --> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/> <property name="username" value="root"/> <property name="password" value="xxbsxy"/> </dataSource> </environment> </environments> <mappers> <!-- Mapper注册:指定Mybatis映射文件的具体位置 --> <!-- mapper标签:配置一个具体的Mapper映射文件 --> <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --> <!-- 对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --> <mapper resource="mappers/EmployeeMapper.xml"/> </mappers> </configuration>
运行和测试
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 public class test { @Test public void testSelectEmployee() throws IOException { // 1.创建SqlSessionFactory对象 // ①声明Mybatis全局配置文件的路径 String mybatisConfigFilePath = "mybatis-config.xml"; // ②以输入流的形式加载Mybatis配置文件 InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath); // ③基于读取Mybatis配置文件的输入流创建SqlSessionFactory对象 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2.使用SqlSessionFactory对象开启一个会话 SqlSession session = sessionFactory.openSession(); // 3.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象(动态代理技术) EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class); // 4. 调用代理类方法既可以触发对应的SQL语句 Employee employee = employeeMapper.getEmployeeById(1); System.out.println("employee = " + employee); // 4.关闭SqlSession session.commit(); //提交事务 [DQL不需要,其他需要] session.close(); //关闭会话 } }
数据输入 单个简单类型参数 单个简单类型参数,在#{}中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。
1 2 3 4 5 6 7 8 public interface EmployeeMapper { Employee getEmployeeById(int id); } <select id="getEmployeeById" resultType="com.cgdcgd.cc.demo.Employee"> select emp_id empId,emp_name empName, emp_salary empSalary from t_emp where emp_id = #{abcd} </select>
传入单个实体对象 1 2 3 4 5 6 7 public interface EmployeeMapper { int insertEmployee(Employee employee); } <insert id="insertEmployee"> insert into t_emp (emp_name, emp_salary ) values (#{empName}, #{empSalary}) </insert>
传入多个类型 1 2 3 4 5 6 7 8 //多个类型参数需要使用@Param注解指定名称 public interface EmployeeMapper { int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary); } <update id="updateEmployee"> update t_emp set emp_salary=#{empSalary} where emp_id=#{empId} </update>
传入 Map 类型参数 1 2 3 4 5 6 7 8 9 //多个类型参数需要使用@Param注解指定名称 public interface EmployeeMapper { int updateEmployeeByMap(Map<String, Object> paramMap); } // 传入map的key即可 <update id="updateEmployeeByMap"> update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey} </update>
数据输出 输出单个简单类型 1 2 3 4 5 6 7 public interface EmployeeMapper { int selectEmpCount(); } <select id="selectEmpCount" resultType="int"> select count(*) from t_emp </select>
select 标签,通过 resultType 指定查询返回值类型!
resultType = “全限定符 | 别名 | 如果是返回集合类型,写范型类型即可
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
1 2 3 4 <typeAliases > <typeAlias alias ="Author" type ="domain.blog.Author" /> <typeAlias alias ="Blog" type ="domain.blog.Blog" /> </typeAliases >
当这样配置时,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
1 <typeAliases > <package name ="domain.blog" /> </typeAliases >
每一个在包 domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
1 2 3 4 @Alias("author") public class Author { ... }
下面是 Mybatis 为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名
映射的类型
_byte
byte
_char (since 3.5.10)
char
_character (since 3.5.10)
char
_long
long
_short
short
_int
int
_integer
int
_double
double
_float
float
_boolean
boolean
string
String
byte
Byte
char (since 3.5.10)
Character
character (since 3.5.10)
Character
long
Long
short
Short
int
Integer
integer
Integer
double
Double
float
Float
boolean
Boolean
date
Date
decimal
BigDecimal
bigdecimal
BigDecimal
biginteger
BigInteger
object
Object
object[]
Object[]
map
Map
hashmap
HashMap
list
List
arraylist
ArrayList
collection
Collection
输出单个实体类型 1 2 3 4 5 6 7 public interface EmployeeMapper { Employee selectEmployee(Integer empId); } <select id="selectEmployee" resultType="com.cgdcgd.cc.demo.Employee"> select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{id} </select>
通过给数据库表字段加别名,让查询结果的每一列都和 Java 实体类中属性对应起来。
增加全局配置自动识别对应关系
在 Mybatis 全局配置文件中,做了下面的配置,select 语句中可以不给字段设置别名
1 2 3 4 5 6 7 8 9 10 11 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> </settings >
输出 Map 类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public interface EmployeeMapper { Map<String,Object> selectEmpNameAndMaxSalary(); } <!-- Map<String,Object> selectEmpNameAndMaxSalary(); --> <!-- 返回工资最高的员工的姓名和他的工资 --> <select id="selectEmpNameAndMaxSalary" resultType="map"> SELECT emp_name 员工姓名, emp_salary 员工工资, (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资 FROM t_emp WHERE emp_salary=( SELECT MAX(emp_salary) FROM t_emp ) </select>
输出 List 类型 1 2 3 4 5 6 7 8 9 public interface EmployeeMapper { List<Employee> selectAll(); } // resultType为List的泛型参数 不需要写List <select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee"> select emp_id empId,emp_name empName,emp_salary empSalary from t_emp </select>
输出自增长类型主键 1 2 3 4 5 6 7 8 9 10 11 public interface EmployeeMapper { int insertEmployee(Employee employee); } <!-- 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>
非自增长类型主键
而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!
使用 selectKey
帮助插入 UUID 作为字符串类型主键示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 <insert id ="insertUser" parameterType ="User" > <selectKey keyProperty ="id" resultType ="java.lang.String" order ="BEFORE" > SELECT UUID() as id </selectKey > INSERT INTO user (id, username, password) VALUES ( #{id}, #{username}, #{password} ) </insert >
在上例中,我们定义了一个 insertUser
的插入语句来将 User
对象插入到 user
表中。我们使用 selectKey
来查询 UUID 并设置到 id
字段中。
通过 keyProperty
属性来指定查询到的 UUID 赋值给对象中的 id
属性,而 resultType
属性指定了 UUID 的类型为 java.lang.String
。
需要注意的是,我们将 selectKey
放在了插入语句的前面,这是因为 MySQL 在 insert
语句中只支持一个 select
子句,而 selectKey
中查询 UUID 的语句就是一个 select
子句,因此我们需要将其放在前面。
最后,在将 User
对象插入到 user
表中时,我们直接使用对象中的 id
属性来插入主键值。
使用这种方式,我们可以方便地插入 UUID 作为字符串类型主键。当然,还有其他插入方式可以使用,如使用 Java 代码生成 UUID 并在类中显式设置值等。需要根据具体应用场景和需求选择合适的插入方式。
多表映射 多表关系回顾:(双向查看)
一对一
夫妻关系,人和身份证号
一对多| 多对一
用户和用户的订单,锁和钥匙
多对多
老师和学生,部门和员工 实体类设计关系(查询):(单向查看)
对一 : 夫妻一方对应另一方,订单对应用户都是对一关系
实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!
例如:
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 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!-- 创建resultMap实现“对一”关联关系映射 --> <!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” --> <!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 --> <resultMap id="selectOrderWithCustomerResultMap" type="order"> <!-- 先设置Order自身属性和字段的对应关系 --> <id column="order_id" property="orderId"/> <result column="order_name" property="orderName"/> <!-- 使用association标签配置“对一”关联关系 --> <!-- property属性:在Order类中对一的一端进行引用时使用的属性名 --> <!-- javaType属性:一的一端类的全类名 --> <association property="customer" javaType="customer"> <!-- 配置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>
对应关系可以参考下图:
对多映射 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实现从Customer到OrderList的“对多”关联关系 --> <resultMap id="selectCustomerWithOrderListResultMap" type="customer"> <!-- 映射Customer本身的属性 --> <id column="customer_id" property="customerId"/> <result column="customer_name" property="customerName"/> <!-- collection标签:映射“对多”的关联关系 --> <!-- property属性:在Customer类中,关联“多”的一端的属性名 --> <!-- ofType属性:集合属性中元素的类型 --> <collection property="orderList" ofType="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>
对应关系可以参考下图:
多表映射优化
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 <resultMap id ="teacherMap" type ="teacher" > <id property ="tId" column ="t_id" /> <collection property ="students" ofType ="student" > <id property ="sId" column ="s_id" /> </collection > </resultMap >
多表映射总结
关联关系
配置项关键词
所在配置文件和具体位置
对一
association 标签/javaType 属性/property 属性
Mapper 配置文件中的 resultMap 标签内
对多
collection 标签/ofType 属性/property 属性
Mapper 配置文件中的 resultMap 标签内
动态语句 where 和 if 标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <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 >
set 标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <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 >
choose/when/otherwise 标签 在多个分支条件中,仅执行一个。
从上到下依次执行条件判断
遇到的第一个满足条件的分支会被采纳
被采纳分支后面的分支都将不被考虑
如果所有的 when 分支都不满足,那么就执行 otherwise 分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="selectEmployeeByConditionByChoose" resultType ="com.cgdcgd.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 >
foreach 标签 基本用法
批量插入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <foreach collection ="empList" item ="emp" separator ="," open ="values" index ="myIndex" > (#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender}) </foreach >
批量删除
1 2 3 4 5 6 7 8 int deleteBatch(@Param("ids") List<Integer> ids) <delete id="deleteBatch"> delete from t_emp where id in <foreach collection="ids" item="id" separator="," open="(" close=")"> #{id} </foreach> </delete>
批量更新时需要注意
上面批量插入的例子本质上是一条 SQL 语句,而实现批量更新则需要多条 SQL 语句拼起来,用分号分开。也就是一次性发送多条 SQL 语句让数据库执行。此时需要在数据库连接信息的 URL 地址中设置:
1 cgdcdg.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true
对应的 foreach 标签如下:
1 2 3 4 5 6 <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 注解指定的名称来引用传入的参数。