MyBatis
MyBatis简介
MyBatis作用
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- MyBatis可以使用简单的XML用于配置和原始映射,将接口和Java的POJO类映射成数据库中的记录
- 使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
历史
- 原是apache的一个开源项目iBatis
- 2010年6月这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
- iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。
为什么要使用MyBatis?
JDBC
- SQL夹在Java代码块里,耦合度高导致硬编码内伤
- 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见
- 要自已创建connection、创建statement、手动设置参数、结果集检索等
Hibernate
- 长难复杂SQL,对于Hibernate而言处理也不容易
- 内部自动生产的SQL,不容易做特殊优化。
- 基于全映射的全自动框架,javaBean存在大量字段时无法只映射部分字段。导致数据库性能下降。
Mybatis
- 对开发人员而言,核心sql还是需要自己优化
- MyBatis是一个半自动化的持久化层框架。
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
MyBatis入门程序
1.下载Mybatis核心包
http://www.mybatis.org/mybatis-3/getting-started.html
https://github.com/mybatis/mybatis-3/releases
2.创建工程,引入MyBatis核心包及依赖包
ant-1.9.6.jar
ant-launcher-1.9.6.jar
asm-5.2.jar
cglib-3.2.5.jar
commons-logging-1.2.jar
javassist-3.22.0-GA.jar
junit-4.9.jar
log4j-1.2.17.jar
log4j-api-2.3.jar
log4j-core-2.3.jar
lombok.jar
mybatis-3.4.6.jar
mysql-connector-java-5.1.7-bin.jar
ognl-3.1.16.jar
slf4j-api-1.7.25.jar
slf4j-log4j12-1.7.25.jar
3.创建customer表,建立与表对象的domain
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter@Getter@ToString
public class Customer {
private Integer cust_id;
private String cust_name;
private String cust_profession;
private String cust_phone;
private String email;
}
4.创建MyBatis核心配置文件SqlMappingConfig.xml
<?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>
<!-- spring整合后 environments配置将废除 使用spring中的连接池 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="1234" />
</dataSource>
</environment>
</environments>
</configuration>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- spring整合后 environments配置将废除 使用spring中的连接池 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="1234" />
</dataSource>
</environment>
</environments>
</configuration>
5.创建与表对象的关系映射Mapping文件编写sql语句
<?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="myTest">
<!--根据cust_id查询客户-->
<select id="queryCustomerById" parameterType="Int" resultType="com.le.domain.Customer">
SELECT * FROM `customer` WHERE cust_id = #{cust_id}
</select>
</mapper>
6.在SqlMappingConfig.xml核心配置文件当中引入Mapping
<!--加载映射文件-->
<mappers>
<mapper resource="com/le/domain/Customer.xml"></mapper>
</mappers>
7.创建工厂,执行sql语句
@Test
public void test() throws IOException {
/* //1.sqlSessionFactoryBuilder 加载配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.读取配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMappingConfig.xml");
//3.获取session工厂
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
//4.获取会话 ---JDBC 连接
SqlSession sqlSession = sessionFactory.openSession();*/
SqlSession sqlSession = MybatisUtils.openSession();
//5.执行sql
Customer customer = sqlSession.selectOne("queryCustomerById", 2);
System.out.println(customer);
//6.关闭session
sqlSession.close();
}
MyBatis核心Api
SqlSessionFactoryBuilder
- SqlSessionFactoryBuilder用于创建SqlSessionFacoty
- SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了
- 因为SqlSession是通过SqlSessionFactory创建的
- 所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory
- 创建sqlSession的工厂,是一个接口
- 接口中定义了openSession的不同重载方法
- SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
SqlSession
- 连接到数据库的一个会话
- sqlSession中定义了数据库操作方法。
- 每个线程都应该有它自己的SqlSession实例
- SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围
- 绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
MyBatis架构
MyBatis-查询
MybatisUtils工具类
package com.le.utils;
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;
public class MybatisUtils {
public static final SqlSessionFactory sessionFactory;
static {
//1.sqlSessionFactoryBuilder 加载配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.读取配置文件
InputStream resourceAsStream = null;
try {
resourceAsStream = Resources.getResourceAsStream("SqlMappingConfig.xml");
} catch (IOException e) {
e.printStackTrace();
}
//3.获取session工厂
sessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
}
public static SqlSession openSession(){
return sessionFactory.openSession();
}
}
查询所有客户
<!--查询所有-->
<select id="queryAllCustomer" resultType="com.le.domain.Customer">
SELECT * FROM `customer`
</select>
@Test
public void test2() {
SqlSession sqlSession = MybatisUtils.openSession();
/*查询所有的用户*/
List<Customer> queryAllCustomer = sqlSession.selectList("queryAllCustomer");
for (Customer customer : queryAllCustomer) {
System.out.println(customer);
}
sqlSession.close();
}
根据用户名模糊查询客户
方式1
<!--根据用户名模糊查询客户-->
<select id="querytCustomerByName" parameterType="String" resultType="com.le.domain.Customer">
select * from customer where cust_name like '%{value}%';
</select>
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.openSession();
List<Customer> customers = sqlSession.selectList("querytCustomerByName", "%李%");
for (Customer customer : customers) {
System.out.println(customer);
}
sqlSession.close();
}
方式2
<!--根据用户名模糊查询客户-->
<select id="querytCustomerByName" parameterType="String" resultType="com.le.domain.Customer">
select * from customer where cust_name like #{name};
</select>
总结
parameterType
指定输入参数类型
mybatis通过ognl从输入对象中获取参数值拼接在sql中
resultType
指定输出结果类型
mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中
selectOne
查询一条记录
如果使用selectOne查询多条记录则抛出异常
selectList
可以查询一条或多条记录
#{}和${}
#{}
表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值
自动进行java类型和jdbc类型转换
#{}可以有效防止sql注入
#{}可以接收简单类型值或pojo属性值
如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称
${}
表示拼接sql串
通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换
${}可以接收简单类型值或pojo属性值
如果parameterType传输单个简单类型值,${}括号中只能是value
保存更新删除
添加客户,返回添加过后自增的主键
<!--添加-->
<insert id="insertCustom" parameterType="com.le.domain.Customer">
/*获取插入的最后一个id*/
<selectKey keyColumn="cust_id" keyProperty="cust_id" resultType="Integer" order="AFTER">
select last_insert_id()
</selectKey>
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values (#{cust_name},#{cust_profession},#{cust_phone},#{email})
</insert>
/*添加客户*/
@Test
public void insert(){
SqlSession sqlSession = MybatisUtils.openSession();
Customer customer = new Customer();
customer.setCust_name("后裔2");
customer.setCust_phone("18907897879");
sqlSession.insert("insertCustom",customer);
//当要改动数据库当中的记录时,执行sql时要自己提交事务
//手动提交事务
sqlSession.commit();
System.out.println(customer.getCust_id());
sqlSession.close();
}
更新客户
<!--更新-->
<update id="updateCustomer" parameterType="com.le.domain.Customer">
update `customer` set cust_name=#{cust_name} where cust_id=#{cust_id}
</update>
/*更新操作*/
@Test
public void update(){
SqlSession sqlSession = MybatisUtils.openSession();
Customer customer = sqlSession.selectOne("queryCustomerById", 12);
customer.setCust_name("孙悟空");
sqlSession.update("updateCustomer",customer);
sqlSession.commit();
sqlSession.close();
}
删除客户
<!--删除操作-->
<delete id="deleteCustomer" parameterType="com.le.domain.Customer">
delete from `customer` where cust_id=#{cust_id}
</delete>
/*删除*/
@Test
public void delete(){
SqlSession sqlSession = MybatisUtils.openSession();
Customer customer = sqlSession.selectOne("queryCustomerById", 12);
sqlSession.delete("deleteCustomer",customer);
sqlSession.commit();
sqlSession.close();
}
MyBatis开发DAO
原始Dao开发方法
package com.le.dao;
import com.le.domain.Customer;
import java.util.List;
public interface CustomerDao {
public Customer getCustomerWithId(Integer id);
public List<Customer> getAllCustomer();
public void addCustomer(Customer customer);
public void updateCustomer(Customer customer);
}
package com.le.dao;
import com.le.domain.Customer;
import com.le.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class CustomerDaoImpl implements CustomerDao {
@Override
public Customer getCustomerWithId(Integer id) {
SqlSession sqlSession = MybatisUtils.openSession();
Customer customer = sqlSession.selectOne("queryCustomerById", id);
return customer;
}
@Override
public List<Customer> getAllCustomer() {
SqlSession sqlSession = MybatisUtils.openSession();
List<Customer> customers = sqlSession.selectList("queryAllCustomer");
return customers;
}
@Override
public void addCustomer(Customer customer) {
SqlSession sqlSession = MybatisUtils.openSession();
sqlSession.insert("insertCustom",customer);
}
@Override
public void updateCustomer(Customer customer) {
SqlSession sqlSession = MybatisUtils.openSession();
sqlSession.update("insertCustom",customer);
}
}
@Test
public void test() throws IOException {
//1.sqlSessionFactoryBuilder 加载配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.读取配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMappingConfig.xml");
//3.获取session工厂
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
//4.获取会话 ---JDBC 连接
SqlSession sqlSession = sessionFactory.openSession();
//5.执行sql
Customer customer = sqlSession.selectOne("queryCustomerById", 2);
System.out.println(customer);
//6.关闭session
sqlSession.close();
}
Mapper动态代理
要求
- namespace必须和Mapper接口类路径一致
- id必须和Mapper接口方法名一致
- parameterType必须和接口方法参数类型一致
- resultType必须和接口方法返回值类型一致
过程
package com.le.mapper;
import com.le.domain.Customer;
import java.util.List;
public interface CustomerMapper {
//根据cust_id查询客户
public Customer queryCustomerById(Integer id);
}
<?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="com.le.mapper.CustomerMapper">
<!--根据cust_id查询客户-->
<select id="queryCustomerById" parameterType="Integer"
resultType="com.le.domain.Customer">
SELECT * FROM `customer` WHERE cust_id = #{cust_id}
</select>
</mapper>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.queryCustomerById(1);
System.out.println(customer);
sqlSession.close();
}
selectOne和selectList
- 动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定
- 如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。
参数传递
单个参数
- 可以接受基本类型,对象类型,集合类型的值。
- MyBatis可直接使用这个参数,不需要经过任何处理。
多个参数
- 任意多个参数,都会被MyBatis重新包装成一个Map传入。
- Map的key是param1,param2…,值就是参数的值。
示例
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.queryCustomerById(1,"鲁班大师");
System.out.println(customer);
sqlSession.close();
}
<!--根据cust_id 和 cust_name查询客户-->
<select id="queryCustomerById"
resultType="com.le.domain.Customer">
SELECT * FROM `customer` WHERE cust_id = #{arg0} and cust_name=#{arg1}
</select>
@param命名参数
- 为参数使用@Param起一个名字,
- MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字
示例
public interface CustomerMapper {
/*
* 根据cust_id查询客户
* */
public Customer queryCustomerById(@Param("cust_id") Integer id,@Param("cust_name") String cust_name);
}
<!--根据cust_id 和 cust_name查询客户-->
<select id="queryCustomerById"
resultType="com.le.domain.Customer">
SELECT * FROM `customer` WHERE cust_id = #{arg0} and cust_name=#{arg1}
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.queryCustomerById(1,"鲁班大师");
System.out.println(customer);
sqlSession.close();
}
POJO
- 当这些参数属于我们业务POJO时,我们直接传递POJO
示例
public void insertCustom(Customer customer);
@Test
public void insert(){
SqlSession sqlSession = MybatisUtils.openSession();
Customer customer = new Customer();
customer.setCust_name("后裔2");
customer.setCust_phone("18907897879");
sqlSession.insert("insertCustom",customer);
//当要改动数据库当中的记录时,执行sql时要自己提交事务
//手动提交事务
sqlSession.commit();
System.out.println(customer);
sqlSession.close();
}
<!--添加-->
<insert id="insertCustom" parameterType="com.le.domain.Customer">
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values (#{cust_name},#{cust_profession},#{cust_phone},#{email})
</insert>
Map
- 我们也可以封装多个参数为map,直接传递
示例
public Customer queryCustomerById(Map<String,Object> map);
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("cust_id",1);
hashMap.put("cust_name","鲁班");
Customer customer = mapper.getCustomerWithID(hashMap);*/
System.out.println(customer);
sqlSession.close();
}
<!--根据cust_id 和 cust_name查询客户-->
<select id="queryCustomerById"
resultType="com.le.domain.Customer">
SELECT * FROM `customer` WHERE cust_id = #{cust_id} and cust_name=#{cust_name}
</select>
参数传递源码分析
-
会把参数给放到一个数组当中
-
如果一个参数, 内部处理时,会自动把该参数范围
-
如果是多个参数,内部会做判断
-
判断是否有@param注解
如果没有@param注解
没有注解的话, 就直接使用arg0 arg1...为key 放到map中
并且还会以param1和param2...为key放一份到map中
如果有@param注解
如果有注解的话, 会使用注解当中的值,替换掉默认的arg0和arg1
使用@param中的值,做为key 放到一个map当中
并且还会以param1和param2...为key放一份到map中
MaBatis核心配置文件
properties标签
- 定义属性及读取属性文件
示例
<!-- 是用resource属性加载外部配置文件 -->
<properties resource="db.properties">
<!-- 在properties内部用property定义属性 -->
<!-- 如果外部配置文件有该属性,则内部定义属性被外部属性覆盖 -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="1234" />
</properties>
settings标签
- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为
示例
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter@Getter@ToString
public class Customer {
private Integer cust_id;
private String cust_name;
private String cust_profession;
private String cust_phone;
private String email;
}
<!-- 用来配置MyBatis中一些设置 -->
<!-- 开启驼峰映射,为自定义的SQL语句服务 -->
<!-- 设置其用数据库字段下划线映射到jaba对象的驼峰式命名属性,默认为false -->
<settings>
<!-- 打印查询语句 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--开启驼峰命名法-->
<setting name="mapUnderscoreToCamelCase" value="true">
</settings>
typeAliases标签
- 类型别名是为 Java 类型设置一个短的名字
- 定义单个别名
<!--定义别名-->
<typeAliases>
<!--单个别名定义-->
<typeAlias alias="Customer" type="com.le.domain.Customer"/>
</typeAliases>
<!--查询用户 ID-->
<select id="getCustomerWithID" resultType="Customer" >
select * from `customers` where cust_id = #{id} and cust_name=#{name}
</select>
-
批量别名定义
如果当前包类与子包类重名,会有异常
可以在类上使用注解@Alias("别名")
<!--定义别名-->
<typeAliases>
<!--批量定义别名, 别名为类名(大小写不敏感)-->
<package name="com.le.domain"/>
</typeAliases>
typeHandlers标签
- 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,
- 还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
- JDK1.8之后实现全部的JSR310规范
- 日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)
- 编写的各种日期时间类型处理器。
- MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的
Plugins标签
- 插件是MyBatis提供的一个非常强大的机制,
- MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。
- 通过插件来修改MyBatis的一些核心行为。
Environments标签
- MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
- 每种环境使用一个environment标签进行配置并指定唯一标识符
- 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
- Environment子标签
transactionManager事务管理
Type有以下取值
JDBC
使用JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围
MANAGED
不提交或回滚一个连接、让容器来管理事务的整个生命周期
ManagedTransactionFactory
自定义
实现TransactionFactory接口
type=全类名/别名
dataSource数据源
type有以下取值
UNPOOLED
不使用连接池UnpooledDataSourceFactory
POOLED
使用连接池PooledDataSourceFactory
JNDI
在EJB 或应用服务器这类容器中查找指定的数据源
自定义
实现DataSourceFactory接口,定义数据源的获取方式
实际开发
实际开发中我们使用Spring管理数据源
并进行事务控制的配置来覆盖上述配置
<!-- spring整合后 environments配置将废除 使用spring中的连接池 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
<environment id="test">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC" />
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
databaseIDProvider标签
MyBatis 可以根据不同的数据库厂商执行不同的语句。
可以通过databaseIDProvider标签来进行设置
<databaseIdProvider type="DB_VENDOR">
<property name="MYSQL" value="mysql"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
示例
<!--查询用户 ID-->
<select id="getAllCustomer" resultType="Customer" databaseId="mysql">
select * from `customers`
</select>
mappers标签
resource属性
使用相对于类路径的资源
<!--加载映射文件-->
<mappers>
<mapper resource="com/le/mapping/CustomerMapping.xml"></mapper>
</mappers>
class属性
- 使用mapper接口类路径
- 此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中
<!--加载映射文件-->
<mappers>
<mapper class="com.le.mapping.CustomerMapping"></mapper>
</mappers>
package子标签
- 指定包下的所有mapper接口
- 此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中
<!--加载映射文件-->
<mappers>
<package name="com.le.mapping"/>
</mappers>
输出类型
输出简单类型
<!--查询总数-->
<select id="getAccountCustomer" resultType="Integer">
select count(*) from customer;
</select>
Map
第1种形式
-
key:是列名
-
value:是列名对应的值
示例
public Map<String,Object> getCustomerWithId(Integer id);
<select id="getCustomerWithId" resultType="java.util.Map">
select * from customer where cust_id=#{id}
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
Map<String, Object> customer = customerMapper.getCustomerWithId(2);
System.out.println(customer);
sqlSession.close();
}
第2种形式
- Map<key,自定义对象>
- key为自己指定的数据库中的列值
示例
@MapKey("cust_name")
public Map<Integer,Customer> getAllCustomer();
<select id="getAllCustomer" resultType="com.le.domain.Customer">
select * from customer;
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
Map<Integer, Customer> allCustomer = customerMapper.getAllCustomer();
for(Integer integer:allCustomer.keySet())
{
System.out.println("key="+integer+" value="+allCustomer.get(integer));
}
sqlSession.close();
}
resultMap
- 只有在写输出时使用的都是resultType
- 但是resultType要求必须得要字段名称和数据库当中的名称一致时才能有值,否则为null
- 如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系
- 表名与domain
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter@Getter@ToString
public class Customer {
private Integer cust_ids;
private String cust_names;
private String cust_professions;
private String cust_phones;
private String email;
}
<resultMap id="customerMap" type="Customer">
<id column="cust_id" property="cust_ids"/>
<result column="cust_name" property="cust_names"/>
<result column="cust_profession" property="cust_professions"/>
<result column="cust_phone" property="cust_phones"/>
<result column="email" property="email"/>
</resultMap>
<select id="getCustomer" resultMap="customerMap">
select * from customer where cust_id=#{id}
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = customerMapper.getCustomer(2);
System.out.println(customer);
sqlSession.close();
}
多表操作
ManyToOne
关系表
查询
分步查询
第一步 先查出所有的订单
<resultMap id="myOrder" type="Order">
<id property="order_id" column="order_id"/>
<result property="order_name" column="order_name"/>
<result property="order_num" column="order_name"/>
<!--分步查询-->
<association property="customer" javaType="Customer"
select="com.le.mapper.CustomerMapper.getCustomerWithId"
column="cust_id">
</association>
</resultMap>
<select id="getOrders" resultMap="myOrder">
select * from `order`
</select>
第二步 根据id查出对应客户
<mapper namespace="com.le.mapper.CustomerMapper">
<!--根据id获取客户-->
<select id="getCustomerWithId" resultType="com.le.domain.Customer">
SELECT * from customer WHERE cust_id = #{id}
</select>
</mapper>
左连接查询
查询所有的订单及订单所对应的客户
- 左连接
- 把左边表的数据全部查出,右边表只查出满足条件的记录
应对sql
SELECT * FROM `order` as o LEFT JOIN customer as c on o.cus_id = c.cust_id;
建立domain
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter@Getter@ToString
public class Order {
private Integer order_id;
private String order_name;
private String order_num;
private Customer customer;
}
建立Mapping映射
<resultMap id="orderMap" type="Order">
<id property="order_id" column="order_id"/>
<result property="order_name" column="order_name"/>
<result property="order_num" column="order_name"/>
<!--关联对象赋值-->
<association property="customer" javaType="Customer">
<id property="cust_id" column="cust_id"/>
<result property="cust_name" column="cust_name"/>
<result property="cust_profession" column="cust_profession"/>
<result property="cust_phone" column="cust_phone"/>
<result property="email" column="email"/>
</association>
</resultMap>
<!--查询所有订单-->
<select id="getAllOrders" resultMap="orderMap">
SELECT * from `order` as o LEFT JOIN `customer` c on o.cust_id = c.cust_id;
</select>
测试类
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List<Order> allOrders = orderMapper.getAllOrders();
for (Order order : allOrders) {
System.out.println(order);
}
sqlSession.close();
}
分部查询懒加载
<!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
<!--指定哪个对象的方法触发一次延迟加载。-->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode"/>
添加
添加客户
<!--保存客户-->
<insert id="insertCustomer" parameterType="Customer"
useGeneratedKeys="true"
keyColumn="cust_id"
keyProperty="cust_id">
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values (#{cust_name},#{cust_profession},#{cust_phone},#{email})
</insert>
设置关系(外键没有赋值上)
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
Customer customer = new Customer();
customer.setCust_name("新客户001");
customer.setCust_phone("137090909090");
customer.setEmail("123123@163.com");
customer.setCust_profession("新职业001");
/*先添加客户 获取客户生成的id 再去添加订单*/
customerMapper.insertCustomer(customer);
Order order = new Order();
order.setOrder_name("新订单001");
order.setOrder_num("20000001001");
/*设置关系 */
order.setCustomer(customer);
System.out.println(customer);
/*保存订单*/
orderMapper.insertOrder(order);
sqlSession.commit();
sqlSession.close();
}
添加订单
<!--保存订单-->
<!-- useGeneratedKeys="true"把新增加的主键赋值到自己定义的keyProperty(id)中 -->
<insert id="insertOrder"
parameterType="Order"
useGeneratedKeys="true"
keyColumn="order_id"
keyProperty="order_id">
insert into `order`(order_name,order_num,cust_id)
values (#{order_name},#{order_num},#{customer.cust_id})
</insert>
OnToMany
查询
查询客户和客户订单
sql语句
SELECT * FROM customer as c LEFT JOIN `order` as o on c.cust_id = o.cust_id;
映射
<resultMap id="custMap" type="Customer">
<id column="cust_id" property="cust_id"/>
<result column="cust_name" property="cust_name"/>
<result column="cust_profession" property="cust_profession"/>
<result column="cust_phone" property="cust_phone"/>
<result column="email" property="email"/>
<collection property="orders" ofType="Order">
<id column="order_id" property="order_id"/>
<id column="order_name" property="order_name"/>
<id column="order_num" property="order_num"/>
</collection>
</resultMap>
<!--查询所有客户-->
<select id="getAllCustomers" resultMap="custMap">
select* from `customer` as c LEFT JOIN `order` as o ON c.cust_id = o.cust_id;
</select>
测试
@Test
public void test4(){
/*查询所有客户*/
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
List<Customer> allCustomers = customerMapper.getAllCustomers();
for (Customer allCustomer : allCustomers) {
System.out.println(allCustomer);
}
sqlSession.close();
}
添加
保存数据
/*保存客户*/
public void insertCustomer(Customer customer);
<!--保存客户-->
<insert id="insertCustomer"
parameterType="Customer"
useGeneratedKeys="true"
keyColumn="cust_id"
keyProperty="cust_id">
insert into `customer`(cust_name,cust_profession,cust_phone,email)
values (#{cust_name},#{cust_profession},#{cust_phone},#{email})
</insert>
/*保存订单*/
public void insertOrder(Order order);
<!--保存订单-->
<!-- useGeneratedKeys="true"把新增加的主键赋值到自己定义的keyProperty(id)中 -->
<insert id="insertOrder"
parameterType="Order"
useGeneratedKeys="true"
keyColumn="order_id"
keyProperty="order_id">
insert into `order`(order_name,order_num,cust_id)
values (#{order_name},#{order_num},#{customer.cust_id})
</insert>
维护外键
/*更新与客户的关系*/
public void updateCustId(@Param("orderId") Integer orderId, @Param("custId") Integer custId);
<!--更新关系-->
<update id="updateCustId">
update `order` set `cust_id`=#{custId} where `order_id`=#{orderId}
</update>
管理关系
@Test
public void test5(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
Customer customer = new Customer();
customer.setCust_name("新客户");
Order order1 = new Order();
order1.setOrder_name("订单2");
Order order2 = new Order();
order2.setOrder_name("订单2");
customer.getOrders().add(order1);
customer.getOrders().add(order2);
/*保存数据*/
customerMapper.insertCustomer(customer);
orderMapper.insertOrder(order1);
orderMapper.insertOrder(order2);
/*更新关系*/
for (Order order : customer.getOrders()) {
orderMapper.updateCustId(order.getOrder_id(),customer.getCust_id());
}
sqlSession.commit();
sqlSession.close();
}
删除
-
删除时一定要先打破关系再做删除操作
示例
/*根据id删除客户*/
public void deleteCustomer(Integer id);
<!--根据id删除客户-->
<delete id="deleteCustomer">
delete from `customer` where cust_id=#{id}
</delete>
/*打破跟客户关系*/
public void updateRelationCustomer(Integer custId);
<!--打破跟客户关系-->
<update id="updateRelationCustomer">
update `order` set cust_id=null where cust_id=#{custId}
</update>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
//一对多删除之前 要先打破关系
orderMapper.updateRelationCustomer(36);
/*删除客户*/
customerMapper.deleteCustomer(36);
sqlSession.commit();
sqlSession.close();
}
ManyToMany
关系表
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
@Setter@Getter@ToString
public class Teacher {
private Integer teacher_id;
private String teacher_name;
private List<Student> students = new ArrayList<>();
}
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter@Getter @ToString
public class Student {
private Integer stu_id;
private String stu_name;
}
查询
分步查询
查询出指定的老师
public interface TeacherMapper {
/*查询指定的老师*/
public Teacher getTeacherWithId(Integer id);
}
<!--查询指定的老师-->
<select id="getTeacherWithId" resultMap="teacherMap2">
SELECT * from teacher WHERE teacher_id = #{id};
</select>
<resultMap id="teacherMap2" type="Teacher">
<id column="teacher_id" property="teacher_id"/>
<result column="teacher_name" property="teacher_name"/>
<collection property="students" ofType="Student"
select="com.le.mapper.StudentMapper.getStuByTeach"
column="teacher_id"/>
</resultMap>
根据老师id查询出所有学生
public interface StudentMapper {
/*根据老师id查询学生*/
public List<Student>getStuByTeach(Integer id);
}
<select id="getStuByTeach" resultType="com.le.domain.Student">
SELECT * from student where stu_id in(SELECT stu_id from stu_teacher_rel where teacher_id = #{id})
</select>
查询
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.openSession();
TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = teacherMapper.getTeacherWithId(2);
System.out.println(teacher);
sqlSession.close();
}
左边接查询
public interface TeacherMapper {
/*查询老师 并且把关联的学生也查出来*/
public List<Teacher>getAllTeachers();
}
<mapper namespace="com.le.mapper.TeacherMapper">
<!-- 查询老师 并且把关联的学生也查出来 -->
<resultMap id="teacherMap" type="Teacher">
<id column="teacher_id" property="teacher_id"/>
<result column="teacher_name" property="teacher_name"/>
<collection property="students" javaType="list" ofType="Student">
<id column="stu_id" property="stu_id"/>
<result column="stu_name" property="stu_name"/>
</collection>
</resultMap>
<select id="getAllTeachers" resultMap="teacherMap">
SELECT*FROM teacher as t
LEFT JOIN stu_teacher_rel as r on t.teacher_id = r.teacher_id
LEFT JOIN student as s ON r.stu_id = s.stu_id
</select>
</mapper>
添加
添加老师
/*保存老师*/
public void insertTeacher(Teacher teacher);
<!--保存老师-->
<insert id="insertTeacher" parameterType="Teacher"
useGeneratedKeys="true"
keyProperty="teacher_id"
keyColumn="teacher_id">
insert into`teacher`(teacher_name) values (#{teacher_name})
</insert>
添加学生
/*保存学生*/
public void insertStudent(Student student);
<!--保存学生-->
<insert id="insertStudent" parameterType="Student"
useGeneratedKeys="true"
keyProperty="stu_id"
keyColumn="stu_id">
insert into `student` (stu_name) values (#{stu_name})
</insert>
添加中间关系
/*插入关系表*/
public void insertRelation(@Param("stuId") Integer stuId, @Param("teacherId") Integer teacherId);
<!-- 插入关系表 -->
<insert id="insertRelation">
insert into stu_teacher_rel (stu_id,teacher_id) values (#{stuId},#{teacherId})
</insert>
动态sql
什么是动态sql
通过mybatis提供的各种标签方法实现动态拼接sql。
if标签
需求
根据客户名和职业查询客户
/*根基客户名称和职业来查询*/
public List<Customer> getCustomer(@Param("name") String name, @Param("profession") String profession);
<select id="getCustomer"
resultType="com.le.domain.Customer">
select from customer where cust_name=#{name} ad cust_profession=#{profession}
</select>
存在问题
- 有可能传入的名称或级别为空
- 可以使用if标签来进行判断
- 如果前一个条件后面多一个and执行就会报错(也就是说需要将and分布合理)
<!--if标签:符合条件会自动把if中的内容拼接到sql之后-->
<select id="getCustomer" resultType="com.le.domain.Customer">
select * from `customer` where
<if test="name != null and name != ''">
`cust_name`=#{name}
</if>
<if test="profession != null and profession!=''">
and `cust_profession`=#{profession}
</if>
</select>
Where标签
- 去掉第一个前And
<!--where标签:会自动生成和删除where
还能删除where后第1个and
条件前and去掉 -->
<select id="getCustomer2"
resultType="com.le.domain.Customer">
select * from `customer`
<where>
<if test="name != null and name != ''">
and `cust_name`=#{name}
</if>
<if test="profession != null and profession!=''">
and `cust_profession`=#{profession}
</if>
</where>
</select>
trim标签
<!--trim标签:
prefix:设置前缀 在第一个条件之前加一个前缀
prefixOverrides: 条件前缀覆盖 把第一个条件之前的and变成空
suffix: 设置后缀 在最后一个条件之后加一个后缀
suffixOverrides: 条件后缀覆盖 把最后一个条件之后的and变成空
-->
<select id="getCustomer3"
resultType="com.le.domain.Customer">
select * from `customer`
<trim prefix="where" prefixOverrides="and" suffixOverrides="and" >
<if test="name != null and name != ''">
and `cust_name`=#{name} and
</if>
<if test="profession != null and profession!=''">
`cust_profession`=#{profession} and
</if>
</trim>
</select>
choose标签
<!--
choose 只要第一个条件满足,后面条件都不执行
when
otherwise
-->
<select id="getCustomer" resultType="com.le.domain.Customer">
<include refid="selectID"/>
<where>
<choose>
<when test="profession != null and profession!=''">
`cust_profession`=#{profession}
</when>
<when test="name != null and name != ''">
`cust_name`=#{name}
</when>
<otherwise>1=1</otherwise>
</choose>
</where>
</select>
set标签
<!--
set标签:会添加update 中 set 并且它会把最后一个,去掉
-->
<!--更新客户-->
<update id="updateCustomer">
update `customer`
<set>
<if test="cust_name != null and cust_name !='' ">
cust_name=#{cust_name},
</if>
<if test="cust_profession != null and cust_profession !='' ">
cust_profession=#{cust_profession},
</if>
</set>
where cust_id=#{cust_id}
</update>
foreach标签
查询条件值为指定的值当中
<select id="queryCustomerIn"
resultType="com.le.domain.Customer">
<include refid="selectID"/>where `cust_id` in(2,3,5,6);
</select>
<!--注意 在include当中定义的property 取的时候 要使用${} -->
<sql id="selectID">
<choose>
<when test="${lk} == 2">
select cust_name from `customer`
</when>
<otherwise>
select * from `customer`
</otherwise>
</choose>
</sql>
给定的值可以以三种形式给出
数组
/*根据id查询指定的客户 多个客户*/
public List<Customer> getCustomers(Integer[] array);
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
Integer[] ids = new Integer[4]{2,3,5,6};
List<Customer> customers = customerMapper.getCustomers(ids);
for (Customer customer : customers) {
System.out.println(customer);
}
sqlSession.close();
}
<select id="getCustomers"
parameterType="Integer[]" resultType="com.le.domain.Customer">
select * from `customer` where `cust_id` in
<foreach collection="array" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</select>
List
<select id="getCustomers"
parameterType="List" resultType="com.le.domain.Customer">
select * from `customer` where `cust_id` in
<foreach collection="list" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(4);
list.add(6);
List<Customer> customers = customerMapper.getCustomers(list);
for (Customer customer : customers) {
System.out.println(customer);
}
sqlSession.close();
}
包装类VO
创建Vo
package com.le.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@Setter@Getter@ToString
public class QueryVo {
private Integer[] ids;
private List<Integer> idList;
}
测试
<select id="getCustomers"
parameterType="QueryVo" resultType="com.le.domain.Customer">
<include refid="selectID"/> where `cust_id` in
<foreach collection="idList" open="(" close=")" separator="," item="ids">
#{ids}
</foreach>
</select>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.openSession();
CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
QueryVo queryVo = new QueryVo();
queryVo.setIds(new Integer[]{2,3,4,5});
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(2);
arrayList.add(3);
arrayList.add(4);
arrayList.add(6);
queryVo.setIdList(arrayList);
List<Customer> customers = customerMapper.getCustomers(queryVo);
for (Customer customer : customers) {
System.out.println(customer);
}
sqlSession.close();
}
bind标签
<!--
bind标签:可以取出传入的值,重新处理, 赋值给别外一个值
-->
<select id="getCustomerWithId" resultType="Customer">
<bind name="newId" value="id+2"/>
<include refid="selectID">
<property name="lk" value="2"/>
</include> where cust_id=#{newId}
</select>
Sql片段
- Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。
<select id="getCustomerWithId" resultType="Customer">
<include refid="selectID">
<property name="lk" value="2"/>
</include> where cust_id=#{id}
</select>
<!--注意 在include当中定义的property 取的时候 要使用${} -->
<sql id="selectID">
<choose>
<when test="${lk} == 2">
select cust_name from `customer`
</when>
<otherwise>
select * from `customer`
</otherwise>
</choose>
</sql>
缓存
一级缓存
缓存介绍
- MyBatis中使用缓存来提高其性能。
- 当查询数据时, 会先从缓存中取出数据,如果缓存中没有,再到数据库当中查询
- MyBatis中的缓存分为两种:一级缓存和二级缓存
- 一级缓存是sqlSession级别的,二级缓存是mapper级别的
一级缓存
- 本地缓存 (默认开启)
- 在sqlSession没有关闭之前,再去查询时, 会从缓存当中取出数据,不会重新发送新的sql
一级缓存失效
- 如果在查询之前,执行了增删改 缓存就会失效
- 手动清空缓存
- 如果两次的查询条件不一样,缓存也会失效
- 如果两个查询在不同的sqlsession当中
二级缓存
二级缓存介绍
全局作用域缓存 一个namespace对应一个缓存
如果会话关闭,一级缓存的数据会被保存到二级缓存中
不同namespace查出的数据 ,会放到自己对应的缓存中
现在默认也是打开的
二级缓存使用步骤
1.确保在配置文件当中开启二级缓存
2.在对应的mapper中添加cache标签
eviction
回收策略
flushInterval
刷新间隔
默认不清空
readOnly
是否只读
true
告诉Mybatis是只读操作,不去修改数据
Mybatis为了加快获取速度,会直接将缓存的引用将给用, 不安全, 速度快
false
非只读,有可能修改数据
Mybatis会利用序列化和反序列化复制一份给你 速度慢些
size
可以存放多少个元素
type
可以用来指定自定义的缓存
3.POJO需要实现Serializable接口
注意事项
- 查询的数据都会先放到一级缓存当中
- 只有会话关闭,一级缓存中的数据才会转称到二级缓存中
缓存相关属性
cacheEnabled
只能控制二级缓存的开关
select中useCache
控制的也是二级缓存是否使用
增删改标签中flushCache
一级和二级都会被清空
增删改flushCache默认为true
查询flushCache默认为false
sqlSession.clearCache()
只清除当前session的一级缓存
localCacheScope
本地缓存作用域
取值
SESSION
STATEMENT
STATEMENT可以使用它禁用缓存
缓存使用顺序
- 先到二级缓存当中查找
- 如果二级缓存中没有,就去找一级缓存
- 如果一级缓存中也没有就去到数据库当中查询