MyBatis看这一篇就够了
MyBatis看这一篇就够了
MyBatis是一个优秀的基于Java的持久层框架,它内部封装了JDBC;试用MyBatis框架开发者只需要关注SQL语句本身。而不需要花费大量精力去处理:加载驱动、创建连接、创建Statement等繁杂过程。
MyBatis通过xml或者注解的方式将要执行的各种Statement配置起来,并通过Java对象和Statement中SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射为Java对象返回。
MyBatis和传统JDBC比起来,优势在哪里?
- 数据库创建链接、释放资源造成系统浪费从而影响系统性能;虽然使用连接池也可以解决,但是MyBatis底层帮我们封装好了,简化了操作。
- SQL和Java代码耦合在一起,不利于代码的维护,在开发中Java代码变化的可能性不是很大,但是SQL会因为需求经常变化。MyBatis将SQL从代码中剥离,利于项目的维护。
- 使用PreparedStatement使用占位符的形式不够灵活,经常要加很多判断语句。导致代码可读性,可维护性降低,对于多变的SQL,MyBatis的动态SQL更加灵活。
- JDBC查询到RestSet后我们还需要对结果进行提取,而MyBatis支持将结果直接封装为JavaBean、List、Map、Java基本类型等的数据类型。
MyBatis快速入门
1、新建maven工程
2、添加maven的坐标。此处用到了mybatis、mysql-connector-java、junit、log4j
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
3、编写实体类,这里简单的编写一个User
类
package cn.rayfoo.bean;
import java.io.Serializable;
import java.util.Date;
/**
* @author: rayfoo@qq.com
* @date: 2020/6/6 1:57 上午
* @description:
*/
public class User implements Serializable {
/**用户id*/
private Integer id;
/**用户名*/
private String username;
/**生日*/
private Date birthday;
/**性别*/
private String sex;
/**住址*/
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
public User(Integer id, String username, Date birthday, String sex, String address) {
this.id = id;
this.username = username;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
public User() {
}
}
4、编写持久层接口UserMapper
package cn.rayfoo.mapper;
import cn.rayfoo.bean.User;
import java.util.List;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 2:12 上午
* @Description: 用户的查询接口
*/
public interface UserMapper {
/**
* 查询所有用户
* @return 返回查询到的所有用户
*/
List<User> findAllUser();
}
5、创建MyBatis全局配置文件和映射文件
<?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>
<settings>
<!-- 打印日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="cn.rayfoo.bean"/>
</typeAliases>
<!-- 配置 mybatis 的环境 -->
<environments default="mysql">
<!-- 配置 mysql 的环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的信息:用的是数据源(连接池) -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="cn/rayfoo/mapper/UserMapper.xml"/>
<mapper resource="cn/rayfoo/mapper/HeroMapper.xml"/>
</mappers>
</configuration>
<?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="cn.rayfoo.mapper.UserMapper">
<!-- 配置查询所有操作 -->
<select id="findAllUser" resultType="cn.rayfoo.bean.User">
select id,username,birthday,sex,address from `user`
</select>
</mapper>
6、编写测试类进行测试
package cn.rayfoo.test;
import cn.rayfoo.bean.User;
import cn.rayfoo.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.List;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description:
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//1、读取全局配置文件
InputStream conf = Resources.getResourceAsStream("MyBatis-cfg.xml");
//2、创建工厂对象SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
//3、使用工厂生产SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//4、创建接口的代理类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//5、执行查询方法
List<User> allUser = userMapper.findAllUser();
//6、查看结果
for (User user : allUser) {
System.out.println(user);
}
//7、释放资源
sqlSession.close();
conf.close();
}
}
MyBatis执行流程
- 实现由SQLSessionFactoryBuilder加载配置文件
- 然后由SQLSessionFactoryBuilder构建一个SqlSessionFactory
- SqlSessionFactory生产SqlSession
- SqlSession使用Executor来调度MappedStatement执行sql语句
- MappedStatement会根据参数类型和返回值类型 执行并且返回对应类型的数据
此处介绍的略为简单,源码级别的分析详见转自CSDN博客
MyBatis配置文件的顺序
MyBatis配置文件是有先后顺序的,如果顺序不对就会报错,所以要注意其顺序问题。
![image-20200609005353368](/Users/rayfoo/Library/Application Support/typora-user-images/image-20200609005353368.png)
配置打印SQL
在MyBatis中,可以设置打印SQL来检测代码是否正常执行。在MyBatis的全局配置文件中加入如下配置即可。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
配置驼峰转下划线
JavaBean中标准的属性命名规则是驼峰式,但是数据库中字段命名一般都由下划线间隔,解决这个问题就需要在MyBatis中配置驼峰转下划线。在MyBatis的全局配置文件的Settings标签中加入如下配置即可。
<settings>
<!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
配置别名
MyBatis中配置别名的方法如下,直接将对应包中的所有类都配置别名
<typeAliases>
<package name="cn.rayfoo.bean"/>
</typeAliases>
在全局配置文件中加载Mapper文件
使用MyBatis,需要告知其mapper接口对应的配置文件的位置,这个在全局配置文件中配置即可。
如果使用了Spring框架进行整合,就无须在此处配置。
<!-- 告知 mybatis 映射配置的位置 -->
<mappers>
<mapper resource="cn/rayfoo/mapper/UserMapper.xml"/>
<mapper resource="cn/rayfoo/mapper/HeroMapper.xml"/>
</mappers>
MyBatis中Mapper接口使用JavaBean和Map作为参数
在MyBatis中的Mapper接口方法中,支持JavaBean或者Map作为参数直接传递。使用JavaBean需要设置为parameterType的全类名,使用map则需要设置为java.util.Map。
如果配置了别名,类名和map都可以省略包名使用首字母缩写。
#{}和#{}的区别
-
{}
- 使用#{}时,将在SQL作为一个占位符实现PreparedStatment中?的效果,占位符替换后会自动加上一对''
- 由于是试用占位符的形式,#{}可以防止SQL注入
-
{}可以接收基本数据类型和JavaBean
- 如果parameterType传输单个简单类型值,#{}中可以是value或者其他名称
- ${}
- 表示拼接SQL串
- 通过${}可以将parameterType传入的内容拼接在SQL中且不会加上''
- ${}可以接收基本数据类型和JavaBean
- 如果parameterType传输单个简单类型值,#{}中只能是value
- 使用模糊查询时,#{}必须在传参时加匹配符;使用字段名时(例如排序、分组)不能使用#{}只能使用\({},因为#{}会自动加上'',模糊查询也可以使用\){}直接拼接。
MyBatis中Mapper接口的定义规则
- 对应的xml文件的namespace必须和Mapper接口所在包名路径一致。
- 对应的xml文件id必须和接口方法名一致。
- parameterType必须和接口方法参数类型一致。
- resultType必须和接口方法返回值一致。
- 接口名尽量和xml文件名保持一致。
parameterType和resultType
前者用于指定输入参数类型,MyBatis通过OGNL从输入对象中获取参数值拼接在SQL中。
后者将MyBatis查询到的结果一行映射为resultType指定的类型对象;如果有多条对象则分行进行映射,并且将对象放入List容器中。
使用Map作为resultType
如果查询的结果是一个JavaBean对象,接口的返回值可以使用该Bean的类型或者Map<String,Object>来接收。如果作为Map接收,其字段名会作为key,值为value。
/**
* 使用JavaBean作为返回值 查询单个
* @param custId
* @return
*/
Hero findHeroById(Integer custId);
/**
* 使用Map<String,Object>作为返回值 查询单个
* @param custId
* @return
*/
Map<String,Object> findHeroById(Integer custId);
<select id="findHeroById" resultType="cn.rayfoo.bean.Hero">
select * from `customer` where cust_id = #{value}
</select>
<select id="findHeroById" resultType="java.util.Map">
select * from `customer` where cust_id = #{value}
</select>
JavaBean作为返回值输出结果:
ClassName{column1=value,column2=value}
Map作为返回值输出结果:
{column1=value,column2=value}
如果查询的结果是List
/**
* 使用Map<T,Object>作为返回值 查询多个
* @return
*/
@MapKey("cust_id")
Map<Integer,Object> findAllHero();
<select id="findAllHero" resultType="java.util.Map">
select * from `customer`
</select>
[val1={column1=value,column2=value},val2={column1=value,column2=value},....]
推荐:亦或是使用List<Map<String,Object>>作为返回值来接收
/**
* 使用List<Map<String,Object>>作为返回值 查询多个
* @return
*/
List<Map<String,Object>> findAllHero();
<select id="findAllHero" resultType="java.util.Map">
select * from `customer`
</select>
[{column1=value,column2=value},{column1=value,column2=value}....]
其打印结果类似于json串
MyBatis中的插入、删除和更新
insert、update、delete和查询基本一致不做太多说明。
注意点:
1、插入、删除、更新时的#{}以parameterType的属性名称对应,表名后的字段与数据库字段名保持一致。
2、一定要commit,否则数据库不会更新。
3、如果插入中文乱码,请检查数据库的url是否是characterEncoding=utf8,项目的编码是否为UTF8。
4、这三者一般都不设置resultType,默认返回值都是执行sql后数据库修改的行数。
案例:
<!--添加一个英雄-->
<insert id="insertCustomer" parameterType="cn.rayfoo.bean.Hero">
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values(#{custName},#{custProfession},#{custPhone},#{email})
</insert>
MyBatis获取自增主键
方法1、在insert标签中加入子标签
<selectKey keyProperty="id" resultType="int">
select LAST_INSERT_ID()
</selectKey>
方法2、设置insert标签的几个属性
useGeneratedKeys设置为true
keyProperty为数据库中主键字段的名称
keyColumn为数据库中主键字段的名称
useGeneratedKeys="true" keyProperty="id" keyColumn="id"
方法2在MyBatis3.4.0以后的版本支持批量插入获取id,使用方法和普通的插入一致,主键会自动放入parameterType对应集合中对象的每一个id中。
public void addUserBatch(List<UserScope> list) {
this.getSqlSession().insert("userMapper.addUserBatch",list);
System.out.println(Arrays.toString(list.toArray()));
}
<insert id="addUserBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert user(user_name,pass_word,address) values
<foreach collection="list" item="item" separator=",">
( #{item.userName},#{item.passWord},#{item.address} )
</foreach>
</insert>
MyBatis工具类
在使用MyBatis的过程中,我们需要频繁的加载配置文件,创建SqlSession等的繁杂过程,这就和原生JDBC一样有些累赘了,所以我们可以创建一个MyBatis工具类来简化这些重复的操作。
当然了,在和Spring整合后同样可以摒弃这些复杂的操作。
package cn.rayfoo.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/8 10:37 下午
* @Description: Mybatis工具类
*/
public class MyBatisUtils {
/**
* 加载配置文件
*/
private static InputStream conf = null;
/**
* SqlSession工厂
*/
private static SqlSessionFactory sqlSessionFactory = null;
/**
* SqlSession
*/
public static SqlSession sqlSession = null;
/**
* Session状态,true为开启中,false为已经关闭
*/
private static boolean sessionStatus = true;
static {
//1、读取全局配置文件
try {
conf = Resources.getResourceAsStream("MyBatis-cfg.xml");
} catch (IOException e) {
e.printStackTrace();
}
//2、创建工厂对象SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(conf);
//3、使用工厂生产SqlSession
sqlSession = sqlSessionFactory.openSession();
}
/**
* 获取工厂信息
* @param clazz Mapper的字节码文件
* @return
*/
public static Object getMapper(Class clazz){
//1、如果Session为空或者已经关闭,则重新生产一个Session
if(!sessionStatus){
sqlSession = sqlSessionFactory.openSession();
}
//返回Mapper
return sqlSession.getMapper(clazz);
}
/**
* 返回一个sqlSession
* @return
*/
public static SqlSession getSqlSession(){
//1、如果Session为空或者已经关闭,则重新生产一个Session
if(!sessionStatus){
sqlSession = sqlSessionFactory.openSession();
}
return sqlSession;
}
/**
* 提交、销毁SqlSession
*/
public static void destory(){
//提交、关闭sqlSession
sqlSession.commit();
sqlSession.close();
try {
//关闭文件流
conf.close();
} catch (IOException e) {
e.printStackTrace();
}
sessionStatus = false;
}
private MyBatisUtils(){
}
}
测试工具类
package cn.rayfoo.test;
import cn.rayfoo.bean.Hero;
import cn.rayfoo.mapper.HeroMapper;
import cn.rayfoo.util.MyBatisUtils;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description: 测试类
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
HeroMapper mapper = (HeroMapper) MyBatisUtils.getMapper(HeroMapper.class);
//创建一个英雄
Hero hero = new Hero("龙女","战士","10086","rayfoo@qq.com");
//添加英雄
mapper.insertCustomer(hero);
//提交事务
MyBatisUtils.destory();
}
}
Mapper接口中多参数问题
在开发过程中Mapper接口难免会遇到需要多个参数的时候,那么此时在xml中如何映射呢?
1、在MyBatis中,Mapper接口中的方法如果使用了多个参数,MyBatis会将这些参数打包到一个Map中,Map的key是param1、param2。。。以此类推,值也就是参数对应的值。MyBatis也允许使用#{arg0}、#{arg1}。。。的形式,如果是对象类型,就要使用#{param1.属性名}
2、MyBatis中还可以使用@Param注解来指定该属性的Key值,直接在参数之前加上@Param("key")即可。如果指定自定义key,仍然可以使用param1、param2。。的形式,但是不再允许使用arg0、arg1。。的形式了。
3、注意:尽量不要使用arg0、arg1。。。的形式,param是从1开始,arg是从0开始。
resultMap
resultType使用JavaBean作为返回值时,要求JavaBean的属性名和数据库字段名保持一致(驼峰转下划线为特例),使用resultMap可以解决这个问题。它的一大作用就是来映射数据库的字段名和JavaBean的属性名。如果查询时只想查询部分字段,同样可以使用resultMap来实现。
- 在Mapper接口对应的XML文件中加入resultMap标签,一般写在xml的最上方且命名为javaBean首字母小写+Map的形式。
- 其id属性必须唯一,可以被其他标签的resultMap属性指定。
- type属性用于指定其对应的JavaBean的全类名(或者别名,但必须配置别名)。
- id子标签用于指定数据库中的主键
- result子标签用于指定数据库中普通字段
- 如果映射的不是全部字段,则查询时不会查询没有被映射的字段。
- id和result都存在column和property两个属性,其中column映射数据库字段名,property映射JavaBean的属性名。
- id和result都存在javaType和jdbcType两个属性,分别用来指定该标签中数据库和Java类型的属性,如果是一般类型的数据,这两个属性可以省略。
<resultMap id="heroMap" type="cn.rayfoo.bean.Hero">
<id column="cust_id" property="custId"></id>
<result column="cust_name" property="custName"></result>
<result column="cust_phone" property="custPhone"></result>
<result column="cust_profession" property="custProfession"></result>
<result column="email" property="email"></result>
</resultMap>
多表操作
一对多关系
在数据库中,经常会遇到一对多查询,那么常见的一对多关系比如部门>员工,班级>学员。。等等。遇到这类的数据库关系,就需要进行一对多映射。
一对多关系如何在数据库中建立?
在一对多关系中,通常会在多的一方建立一个外键用于保存一的一方的主键。
例如已有客户表,现在建立一个订单表,那么客户表无需更改,只需要在新建立的订单表中加入order原有的"order_id"、“order_name”、“order_num”以及"cust_id"四个字段即可。其中“cust_id”添加外键约束。
一对一关系如何在Java中映射?
在java类中,如果要映射一对多关系,一般是在多的一方中取消一对多中的“一方”外键属性的声明,使用一对多中的“一方”所在的java类取代。
public class Order {
/** 订单号 */
private Integer orderId;
/** 订单名称 */
private String orderName;
/** 订单金额 */
private Double orderPrice;
/** 下单人 */
private Customer customer;
getter setter 构造...
}
一对多关系如何在MyBatis中映射?
MyBatis中映射对多关系有多种方法,其中比较简单易于理解的就是使用连缀的方式,映射多对一中的“一方”的字段和属性。
<resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
<id property="orderId" column="order_id"></id>
<result property="orderName" column="order_name"></result>
<result property="orderPrice" column="order_price"></result>
<result property="customer.custId" column="cust_id"></result>
<result property="customer.custName" column="cust_name"></result>
<result property="customer.custProfession" column="cust_profession"></result>
<result property="customer.custPhone" column="cust_phone"></result>
</resultMap>
案例:
<select id="findOrders" resultMap="baseResultMap">
select * from `order` o left join `customer` c on o.cust_id = c.cust_id
</select>
public interface OrderMapper {
List<Order> findOrders();
}
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
OrderMapper mapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
//获取全部订单
List<Order> orders = mapper.findOrders();
for (Order order : orders) {
System.out.println(order);
}
//销毁
MyBatisUtils.destory();
}
}
不映射直接使用Map同样可以解多对一多映射问题
<select id="findAllOrder" resultType="java.util.Map">
select * from `order` o left join `customer` c on o.cust_id = c.cust_id
</select>
public interface OrderMapper {
List<Map<String,Object>> findAllOrder();
}
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
OrderMapper mapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
//获取全部订单
List<Map<String, Object>> allOrder = mapper.findAllOrder();
for (Map<String, Object> item : allOrder) {
System.out.println(item);
}
//销毁
MyBatisUtils.destory();
}
}
{cust_profession=战士, order_price=1536.29, cust_name=龙女, order_id=1, cust_id=13, cust_phone=10086, email=rayfoo@qq.com, order_name=花呗还款}
{cust_profession=战士, order_price=4000.0, cust_name=龙女, order_id=2, cust_id=14, cust_phone=10086, email=rayfoo@qq.com, order_name=基金加仓}
{cust_profession=战士, order_price=3298.0, cust_name=龙女, order_id=3, cust_id=13, cust_phone=10086, email=rayfoo@qq.com, order_name=购买手机}
使用association映射多对一关系
MyBatis提供的对多映射的另一种方式就是使用association,在resultMap中加入association子标签,在该标签中添加property和javaType,分别映射一对多中的“一方”在java类中的属性名,和其类型。在association中可以继续添加id和result来映射一对多中的“一方”。
<resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
<id property="orderId" column="order_id"></id>
<result property="orderName" column="order_name"></result>
<result property="orderPrice" column="order_price"></result>
<association property="customer" javaType="cn.rayfoo.bean.Customer">
<id property="custId" column="cust_id"></id>
<result property="custName" column="cust_name"></result>
<result property="custProfession" column="cust_profession"></result>
<result property="custPhone" column="cust_phone"></result>
</association>
</resultMap>
association级联查询-分步查询
在association标签中,存在一个select属性,其值可以指定一个查询方法的id,如果该查询需要使用到参数,还可以指定column属性为查询参数。
<resultMap id="baseResultMap" type="cn.rayfoo.bean.Order">
<id property="orderId" column="order_id"></id>
<result property="orderName" column="order_name"></result>
<result property="orderPrice" column="order_price"></result>
<association property="customer" javaType="cn.rayfoo.bean.Customer"
select="cn.rayfoo.mapper.CustomerMapper.findCustomer" column="cust_id">
</association>
</resultMap>
association级联查询-分步查询-懒加载
使用association进行分步查询,默认是查询时一定会执行select中的语句,无论是否用到了association中的元素。开启懒加载可以实现当用到association中的元素时执行才执行其对应的select语句。
修改mybatis的全局配置文件即可,当mybatis3.4.1以前都为true,之后的版本默认为false
但是当调用toString方法时,不管是否使用了association中映射的属性值,都会触发延迟加载,去掉lazyLoadTriggerMethods的value中的toString即可。
<settings>
<!-- 打印日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 是否开启自动驼峰命名规则(camel case)映射,即从数据库列名 A_COLUMN 到属性名 aColumn 的类似映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 是否开启懒加载 当false时为按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 哪些方法会触发延迟加载 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode"/>
</settings>
一对多的添加操作
对于一对多映射中的添加操作,应该先添加一的一方获取到一的一方的id才能添加多的一方。例如存在客户表和订单表,要进行客户和订单的添加,那么就需要先添加客户,获取客户的自增id,才能进而添加订单。
<insert id="insertCustomer" parameterType="cn.rayfoo.bean.Customer" useGeneratedKeys="true" keyColumn="cust_id" keyProperty="custId">
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values(#{custName},#{custProfession},#{custPhone},#{email})
</insert>
<insert id="insertOrder" parameterType="cn.rayfoo.bean.Order">
insert into `order`(order_name,order_price,cust_id)
values(#{orderName},#{orderPrice},#{customer.custId})
</insert>
package cn.rayfoo.test;
import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.util.MyBatisUtils;
import java.util.List;
import java.util.Map;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description: 测试类
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
OrderMapper orderMapper = (OrderMapper) MyBatisUtils.getMapper(OrderMapper.class);
//创建顾客
Customer customer = new Customer("盖伦","战士","10011","rayfooQqq.com");
customerMapper.insertCustomer(customer);
//创建订单
Order order1 = new Order("MacBook Pro",888.88,customer);
Order order2 = new Order("MacBook Air",999.99,customer);
orderMapper.insertOrder(order1);
orderMapper.insertOrder(order2);
//销毁
MyBatisUtils.destory();
}
}
一对多的查询操作
当映射的一方是多的一方,JavaBean中使用List来映射,resultMap也不能使用association了,association替换为collection,javaType替换为ofType,其余都没有变化。
分步查询并不见得更快,要结合实际的使用情况,分步查询会发送更多次的sql(每一条数据都会发送一条sql,相当于sql的循环调用),但是分步查询支持懒加载。
<resultMap id="baseResultMap" type="cn.rayfoo.bean.Customer">
<id property="custId" column="cust_id"></id>
<result property="custName" column="cust_name"></result>
<result property="custProfession" column="cust_profession"></result>
<result property="custPhone" column="cust_phone"></result>
<result property="email" column="email"></result>
<collection property="orders" ofType="cn.rayfoo.bean.Order">
<id property="orderId" column="order_id"></id>
<result property="orderName" column="order_name"></result>
<result property="orderPrice" column="order_price"></result>
</collection>
</resultMap>
<select id="findAllCustomer" resultMap="baseResultMap">
select * from `customer` as c left join `order` as o on c.cust_id = o.cust_id
</select>
package cn.rayfoo.test;
import cn.rayfoo.bean.Customer;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.util.MyBatisUtils;
import java.util.List;
import java.util.Map;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description: 测试类
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
List<Customer> allCustomer = customerMapper.findAllCustomer();
for (Customer customer : allCustomer) {
System.out.print(customer);
}
//销毁
MyBatisUtils.destory();
}
}
一对多分步查询(不建议使用)
<resultMap id="baseResultMap" type="cn.rayfoo.bean.Customer">
<id property="custId" column="cust_id"></id>
<result property="custName" column="cust_name"></result>
<result property="custProfession" column="cust_profession"></result>
<result property="custPhone" column="cust_phone"></result>
<result property="email" column="email"></result>
<collection property="orders" ofType="java.util.List" select="cn.rayfoo.mapper.OrderMapper.findOrderById" column="cust_id">
</collection>
</resultMap>
<select id="findOrderById" resultType="cn.rayfoo.bean.Order">
select * from `order` where order_id = #{value}
</select>
/**
* 根据cust_id查询
* @param cust_id
* @return
*/
List<Order> findOrderById(Integer cust_id);
package cn.rayfoo.test;
import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.util.MyBatisUtils;
import java.util.List;
import java.util.Map;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description: 测试类
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
CustomerMapper customerMapper = (CustomerMapper) MyBatisUtils.getMapper(CustomerMapper.class);
List<Customer> allCustomer = customerMapper.findAllCustomer();
for (Customer customer : allCustomer) {
System.out.println(customer);
}
//销毁
MyBatisUtils.destory();
}
}
一对多删除
此时需要注意,如果用户已经不存在了,那么它对应的订单就是垃圾数据了,此时删除用户后应该同时删除掉用户的订单数据。
如果只想删除客户,不删除其关联的订单,可以先打破订单和客户之间的关系。使用update语句将符合条件的客户编号外键改为null即可。
多对多关系
在数据库中,也会遇到一对多查询,那么常见的多对多关系比如老师-学生,学生-课程,用户-角色。。等等。遇到这类的数据库关系,就需要进行多对多映射。
多对多建表原则
多对多关系,一般会建立一个中间表,中间表中创建两个外键存储两个表的主键。
例如现在存在学生表、教师表、则需要创建中间表教师_学生。
-- student table
create table student
(
student_id int auto_increment
primary key,
student_name varchar(20) null
);
-- teacher table
create table teacher
(
teacher_id int auto_increment
primary key,
teacher_name varchar(20) null
);
-- teacher_student table
create table teacher_student
(
id int auto_increment
primary key,
student_id int null,
teacher_id int null,
constraint teacher_student_student_student_id_fk
foreign key (student_id) references student (student_id),
constraint teacher_student_teacher_teacher_id_fk
foreign key (teacher_id) references teacher (teacher_id)
);
在javaBean中创建实体时,无需创建中间表的Bean对象。只需要创建Student、Teacher即可。
在MyBatis的映射中,我们可以将多对多转换为两个一对多,在两方别进行对多映射即可。和一对多的不同之处只有SQL语句。多对多同样支持分步查询。
多对多的添加分为三步,添加学生、添加老师、添加中间表。关系表的插入可以在学生、老师任意一段进行。
/**
* 添加一个学生
* @param student
*/
void insertStudent(Student student);
/**
* 更新学生和老师的关系
* @param teacherId
* @param studentId
*/
void updateRelation(@Param("teacherId") Integer teacherId, @Param("studentId") Integer studentId);
/**
* 添加一个教师
* @param teacher
*/
void insertTeacher(Teacher teacher);
<insert id="insertStudent" parameterType="cn.rayfoo.bean.Student" useGeneratedKeys="true" keyColumn="student_id" keyProperty="studentId">
insert into student(student_name) values(#{studentName})
</insert>
<insert id="updateRelation">
insert into teacher_student(teacher_id,student_id)
values(#{teacherId},#{studentId})
</insert>
<insert id="insertTeacher" parameterType="cn.rayfoo.bean.Teacher" useGeneratedKeys="true" keyColumn="teacher_id" keyProperty="teacherId">
insert into teacher(teacher_name) values(#{teacherName})
</insert>
package cn.rayfoo.test;
import cn.rayfoo.bean.Customer;
import cn.rayfoo.bean.Order;
import cn.rayfoo.bean.Student;
import cn.rayfoo.bean.Teacher;
import cn.rayfoo.mapper.CustomerMapper;
import cn.rayfoo.mapper.OrderMapper;
import cn.rayfoo.mapper.StudentMapper;
import cn.rayfoo.mapper.TeacherMapper;
import cn.rayfoo.util.MyBatisUtils;
import java.util.List;
import java.util.Map;
/**
* @Author: rayfoo@qq.com
* @Date: 2020/6/6 10:01 下午
* @Description: 测试类
*/
public class MyBatisTest {
public static void main(String[] args) throws Exception {
//获取Mapper
TeacherMapper teacherMapper = (TeacherMapper) MyBatisUtils.getMapper(TeacherMapper.class);
StudentMapper studentMapper = (StudentMapper) MyBatisUtils.getMapper(StudentMapper.class);
//创建学生和老师
Student student = new Student("全浩");
Teacher teacher1 = new Teacher("李怡霖");
Teacher teacher2 = new Teacher("闫洪波");
//添加
studentMapper.insertStudent(student);
teacherMapper.insertTeacher(teacher1);
teacherMapper.insertTeacher(teacher2);
//更新关系
studentMapper.updateRelation(teacher1.getTeacherId(),student.getStudentId());
studentMapper.updateRelation(teacher2.getTeacherId(),student.getStudentId());
//销毁
MyBatisUtils.destory();
}
}
MySQL insert语句中中value和values的区别
这里补充一个知识点,在MySQL的insert语句中使用value和values都可以,建议在插入单行的时候使用VALUES,在插入多行的时候使用VALUE;在大多数情况下使用这样的方案效率比较高。而SQL Server中只支持values关键字。
动态SQL
MyBatis中提供了动态SQL的机制,所谓动态sql其实就是动态的拼接sql。MyBatis为我们提供了很多的标签,可以实现类似于java代码中的选择、分支、 循环等功能。通过这些标签可以动态的拼接sql。
if标签
if标签用于判断一个布尔值或者布尔表达式,来判定是否拼接标签内的SQL。一般用于where后条件的非空、忽略等判断。一般都会配合where标签一起使用。
where标签
在使用if标签的过程中会遇到一些问题,比如当if的test都为false时,就会多出一个where标签,当第一个条件为空时,就会多出一个and标签。此时就需要用到where标签
where标签可以自动的生成和删除where关键字、还可以删除where后第一个and关键字。
所以在使用where和if标签的时候 , 尽量使用前置and,不要使用后置and。
trim标签
此标签可以代替where标签来使用,主要的功能是对前缀、后缀的一些操作。
<select id="selectByCondition" resultType="cn.rayfoo.bean.Customer">
select * from `customer`
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="custName != null and custName != ''">
and cust_name = #{custName}
</if>
<if test="profession != null and profession != ''">
and profession = #{profession} and
</if>
</trim>
</select>
此案例中,会在条件之前增加where,去掉条件之前的and、条件之后的and。
choose when otherwise标签
这个标签类似于java中的if else
当when中test的条件成立就只执行when中的语句,不再继续执行。否则执行otherwise中的内容。
其中when可以存在多个,otherwise只能存在一个。
<select id="selectByCondition" resultType="cn.rayfoo.bean.Customer">
select * from `customer`
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<choose>
<when test="custName != null and custName != ''">
and custName = #{cust_name}
</when>
<when test="profession != null and profession !=''">
and profession = #{profession}
</when>
<otherwise>
and 1=1
</otherwise>
</choose>
</trim>
</select>
set标签
在update的操作中 set后同样可以使用if来进行判断,此时如果存在多个if,就可能会出现多出“逗号”的情况,此时可以使用set标签来代替set关键字,set标签可以帮我们去掉最后一个逗号。一般使用set标签时,逗号都放在后面。
<update id="updateCustomer">
update `customer`
<set>
<if test="custName != null and custName != ''">
cust_name = #{custName},
</if>
<if test="custProfession != null and custProfession != ''">
cust_profession = #{custProfession}
</if>
</set>
<where>
cust_id = #{custId}
</where>
</update>
foreach标签
当查询的内容是多个时,可以使用foreach标签。foreach标签可以遍历list或者array类型的数据。
collection属性:指定参数类型,可选list和array,如果是array,那mybatis标签中的参数类型需要指定为对应类型的数组形式,例如parameterType="Integer[]",list则需要设置为java.util.List
open属性:开始标志一般用(
close属性:结束标志一般用)
separator属性:间隔符 一般用逗号
item属性:迭代变量 任意名称
在标签体内可以直接使用#{item对应的值}进行遍历
<select id="findCustomers" parameterType="list" resultType="cn.rayfoo.bean.Customer">
select * from `customer`
<where>
`cust_id` in
<foreach collection="list" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</select>
foreach的高级用法:如果要遍历一个bean对象中的array或者list类型的属性,需要先将parameterType=指定为该对象。collection则指定此属性 其他用法相同。
<select id="findCustomers" parameterType="cn.rayfoo.Customer" resultType="cn.rayfoo.bean.Customer">
select * from `customer`
<where>
`cust_id` in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</select>
bind标签
该标签可以取出传入的值,重新处理,赋值给另一个值。
注意点:如果是普通类型的单值,必须加@Param注解,才能正常取出。如果是对象直接可以获取。
SQL片段
动态SQL支持将一段SQL封装为一个片段,此片段可以在多处执行。
用法:使用
sql片段中可以使用choose、if、等的逻辑判断
缓存机制
MyBatis中使用了缓存机制来提高性能,当查询数据时,会先从缓存中取出数据,如果缓存中没有,再到数据库中查询。
MyBatis中的缓存有两种:一级缓存和二级缓存
一级缓存
一级缓存是SqlSession(一次会话)级别的,二级缓存是Mapper级别的。
一级缓存默认是开启的状态,在SqlSession没有关闭之前,再去查询时,会从缓存中取出数据,不会重新发送新的sql。
一级缓存的失效:发生如下场景时,一级缓存会失效。
- 如果在下次查询之前进行了增、删、改操作,缓存就会失效。
- 手动清空了缓存(sqlSession.clearCache()方法)
- 两次查询的条件不一样,缓存也会失效
- 如果两个查询在不同的SqlSession当中
二级缓存
二级缓存是全局作用域缓存,一个namespace对应一个缓存,如果会话关闭,一级缓存的数据会被保存到二级缓存中,不同的namespace查出的数据,也会放到自己对应的缓存中,默认也是打开的,早期版本呢是false。
二级缓存使用的注意事项
1、确保全局配置文件中已经打开了二级缓存,settings->setting->cacheEnable == true
2、在对应的mapper中添加cache标签
属性介绍
eviction:回收策略LRU(默认)【移除最近最少使用】、FIFO【先进先出】、SOFT、WEAK
flushInterval:刷新间距,单位是毫秒,默认不清空
readOnly:是否只读,取值为true或false,为true是只读mybatis会直接给缓存的引用不安全,但是速度快,非只读mybatis会利用序列化和反序列化复制一份 速度慢
size:可以存放多少个元素 默认的即可,也不能设置的太大,太大容易溢出
type:可以用来指定自定义的缓存,例如redis
3、JavaBean要实现Serializable接口,因为readOnly设置为只读的话,需要进行复制操作。
注意:只要会话关闭后一级缓存中数据才会保存到二级缓存中!
缓存的相关配置
是否可以设置每个mapper方法的缓存是否开启?
在MyBatis的mapper方法中,可以单独的设置是否需要开启缓存,它的优先级是最高的。但是它只能控制二级缓存是否有用,无法影响一级缓存。
如何清空二级缓存?
增删改查标签中存在一个flushCache属性,默认为true,作用是清空一级、二级缓存。
可以手动调用sqlSession.clearCache()手动清空一级缓存。
如何禁用一级缓存?
在settings->setting->localCacheScope设置为STATEMENT,默认为SESSION,设置为STATEMENT可以关闭一级缓存。
缓存使用顺序
1、先到二级缓存找
2、二级缓存没有就去一级缓存找
3、一级缓存也没有就去数据库中找