Mybatis持久层框架技术
(一)Mybatis概述
Mybatis的作用
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
- MyBatis可以使用简单的XML用于配置和原始映射,将接口和Java的POJO类映射成数据库中的记录
- 使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis的优势
- JDBC的SQL夹在Java代码块里,耦合度高导致硬编码内伤,维护不易且实际开发需求中sql是有变化,频繁修改的情况多见。
- Hibernate中对于长难复杂SQL,处理也不容易。且内部自动生产的SQL,不容易做特殊优化。基于全映射的全自动框架,javaBean存在大量字段时无法只映射部分字段。导致数据库性能下降。
- Mybatis对开发人员而言,核心sql还可以根据需求自己优化,是一个半自动化的,支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。
Mybatis架构与核心API
三个核心API:
1. SqlSessionFactoryBuilder
SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了。因为SqlSession是通过SqlSessionFactory创建的,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
2. SqlSessionFactory
创建sqlSession的工厂,是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。
3. SqlSession
连接到数据库的一个会话,sqlSession中定义了数据库操作方法。每个线程都应该有它自己的SqlSession实例,SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围,绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
(二)快速入门
1.下载Mybatis核心包
2.创建工程,引入核心包及依赖包
创建Java工程,导入需要的包,在核心包的lib库中包含必须的核心包,此外当然还需要链接数据库的jar包,以及用于映射类中要使用的lombok,与测试用junit等依赖包。
3.创建表,建立与表对象的domain
创建用户表对应的domain
package com.test.domain;
import lombok.Getter;
import lombok.Setter;
@Setter@Getter
public class Customer {
private int id;
private String name;
private String tel;
private String address;
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", tel='" + tel + '\'' +
", address='" + address + '\'' +
'}';
}
}
4.创建核心配置文件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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="12345"/>
</dataSource>
</environment>
</environments>
</configuration>
5.创建与表对象的关系映射Mapping文件
每一个数据库表都对应一个Mapping文件,在文件中存储着对此表的所有操作。如下文件只包含一个对用户表根据ID进行查询的操作。id=“findCoustomerById” 表示操作的名称,parameterType=“Int” 表示所传参数的数据类型,resultType="com.test.domain.Customer"表示查询结果封装为Customer结果集返回。
<?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">
<select id="findCoustomerById" parameterType="Int" resultType="com.test.domain.Customer">
select * from customer where id= #{id}
</select>
</mapper>
6.在核心配置文件当中引入对应的Mapping
核心配置文件中要包括每一个数据库表的Mapping,在之后的构建工厂中,才能执行将对表的操作。
<?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 default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="12345"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/test/domain/Customer.xml"></mapper>
</mappers>
</configuration>
7.创建工厂,执行sql语句
通过SqlSessionFactoryBuilder加载读取配置文件SqlMappingConfig.xml,生成session工厂sessionFactory,之后就可以联接数据库进行会话,sqlSession.selectOne(“findCoustomerById”, 1)中findCoustomerById为执行操作的名称,与数据表文件中的id对应,1为所传参数。
package com.test.testjunit;
import com.test.domain.Customer;
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 org.junit.Test;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
public class MyTset {
@Test
public void test() throws IOException {
//1.加载配置文件
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 coustomer = sqlSession.selectOne("findCoustomerById", 1);
System.out.println(coustomer);
//6.关闭session
sqlSession.close();
}
}
(三)封装为工具类并应用
如下代码为加载核心配置文件,生产Session工厂,只需执行一次即可,所以我们可以将它封装为工具类Utils。
package com.test.Utils;
import jdk.internal.dynalink.beans.StaticClass;
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 Utils {
public static final SqlSessionFactory sessionFactory;
static{
//1.加载配置文件
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();
}
}
这样在使用时直接调取Utils类中的OpenSession即可。
package com.test.testjunit;
import com.test.Utils.Utils;
import com.test.domain.Customer;
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 org.junit.Test;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
public class MyTset {
@Test
public void test() throws IOException {
// //1.加载配置文件
// 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 = Utils.openSession();
//5.执行SQL
Customer coustomer = sqlSession.selectOne("findCoustomerById", 1);
System.out.println(coustomer);
//6.关闭session
sqlSession.close();
}
}
增删改查的实现
mapping编写如下:
<!--查询所有-->
<select id="findAllCoustomer" resultType="com.test.domain.Customer">
select * from customer
</select>
<!--根据条件查询-->
<select id="findAllCoustomerByname" resultType="com.test.domain.Customer">
select * from customer where name like '%${value}%'
</select>
<!--返回之前插入的ID-->
<insert id="InsertCoustomers" parameterType="com.test.domain.Customer">
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into customer(name,tel,address)values(#{name},#{tel},#{address})
</insert>
<!--条件更新-->
<update id="UpdateCustomer" parameterType="com.test.domain.Customer">
update customer set address=#{address} where id=#{id}
</update>
<!--条件删除-->
<delete id="DeleteCustomer" parameterType="com.test.domain.Customer">
delete from customer where id =#{id}
</delete>
测试类如下
@Test
/**
* 查询所有
*/
public void test2(){
SqlSession sqlSession = Utils.openSession();
//执行SQL
List <Customer> coustomer = sqlSession.selectList("findAllCoustomer");
for (Customer c:coustomer
) {
System.out.println(c);
}
//6.关闭session
sqlSession.close();
}
/**
* 条件查询
*/
@Test
public void test3(){
SqlSession sqlSession = Utils.openSession();
//执行SQL
List <Customer> coustomer = sqlSession.selectList("findAllCoustomerByname","f");
for (Customer c:coustomer
) {
System.out.println(c);
}
//6.关闭session
sqlSession.close();
}
/**
* 插入并返回其ID
*/
@Test
public void test4(){
SqlSession sqlSession = Utils.openSession();
Customer c = new Customer();
c.setName("gpy");
c.setTel("1805611");
c.setAddress("sjz");
//执行SQL
sqlSession.insert("InsertCoustomers", c);
//更改数据库需要手动提交事务
sqlSession.commit();
//6.关闭session
System.out.println(c);
System.out.println("上次插入表ID为"+c.getId());
sqlSession.close();
}
/**
* 条件更新
*/
@Test
public void test5(){
SqlSession sqlSession = Utils.openSession();
Customer c = new Customer();
c.setAddress("sjz");
c.setId(10);
//执行SQL
sqlSession.update("UpdateCustomer", c);
//更改数据库需要手动提交事务
sqlSession.commit();
//6.关闭session
sqlSession.close();
}
/**
* 根据Id删除
*/
@Test
public void test6(){
SqlSession sqlSession = Utils.openSession();
Customer c = new Customer();
c.setId(11);
//执行SQL
sqlSession.delete("DeleteCustomer", c);
//更改数据库需要手动提交事务
sqlSession.commit();
//6.关闭session
sqlSession.close();
}
(三)两种传参方式#{}和¥{}
# {}:
表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入,#{}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
$ {}
表示拼接sql串,通过$ {}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,$ {}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value.
(四)原始Dao开发与Mapper动态代理
学习Mapper动态代理之前介绍一下传统MVC中DAO的开发方法
1.首先创建DAO的接口
2.创建覆盖接口的方法实现类
3.在控制器中new出DAO实现类,再调用其中的方法。
DAO的接口
package com.test.dao;
import com.test.domain.Customer;
import java.util.List;
public interface CustomerDao {
public Customer findCustomerById(int id);
public List<Customer> findAllCustomer();
public void addCustomer (Customer customer);
public void upDateCustomer();
}
方法实现类
package com.test.dao;
import com.test.Utils.Utils;
import com.test.domain.Customer;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class CustomerDaoImpl implements CustomerDao {
@Override
public Customer findCustomerById(int id) {
//1.从工具类调取sqlsession
SqlSession sqlSession = Utils.openSession();
//2.根据SQL进行传参,会话
Customer coustomer = sqlSession.selectOne("findCoustomerById", 1);
sqlSession.close();
//3.返回查询结果
return coustomer;
}
@Override
public List<Customer> findAllCustomer() {
return null;
}
@Override
public void addCustomer(Customer customer) {
}
@Override
public void upDateCustomer() {
}
}
Mapper动态代理
要求
- namespace必须和Mapper接口类路径一致
- id必须和Mapper接口方法名一致
- parameterType必须和接口方法参数类型一致
- resultType必须和接口方法返回值类型一致
过程
namespace必须和Mapper接口类路径一致
<?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.test.mapper.CustomerMapper">
<select id="findCoustomerById" parameterType="Int" resultType="com.test.domain.Customer">
select * from customer where id= #{id}
</select>
<!--查询所有-->
<select id="findAllCoustomer" resultType="com.test.domain.Customer">
select * from customer
</select>
<!--根据条件查询-->
<select id="findAllCoustomerByname" resultType="com.test.domain.Customer">
select * from customer where name like '%${value}%'
</select>
<!--返回之前插入的ID-->
<insert id="InsertCoustomers" parameterType="com.test.domain.Customer">
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into customer(name,tel,address)values(#{name},#{tel},#{address})
</insert>
<!--条件更新-->
<update id="UpdateCustomer" parameterType="com.test.domain.Customer">
update customer set address=#{address} where id=#{id}
</update>
<!--条件删除-->
<delete id="DeleteCustomer" parameterType="com.test.domain.Customer">
delete from customer where id =#{id}
</delete>
</mapper>
id必须和Mapper接口方法名一致
parameterType必须和接口方法参数类型一致
resultType必须和接口方法返回值类型一致
import java.util.List;
public interface CustomerMapper {
/**
* Mapper动态代理
* 1. namespace必须和Mapper接口类路径一致
* 2. id必须和Mapper接口方法名一致
* 3. parameterType必须和接口方法参数类型一致
* 4. resultType必须和接口方法返回值类型一致
*/
public Customer findCoustomerById(int id);
public List<Customer> findAllCoustomer();
public List<Customer> findAllCoustomerByname(String name);
public int InsertCoustomers(Customer customer);
public void UpdateCustomer(Customer customer);
public void DeleteCustomer(Customer customer);
}
控制器实现写法对比
package com.test.testjunit;
import com.test.Utils.Utils;
import com.test.dao.CustomerDao;
import com.test.dao.CustomerDaoImpl;
import com.test.domain.Customer;
import com.test.mapper.CustomerMapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyTest2 {
/**
* 传统DAO
*/
@Test
public void test(){
CustomerDao customerDao = new CustomerDaoImpl();
Customer customer = customerDao.findCustomerById(1);
System.out.println(customer);
}
/**
* Mapper动态代理
*/
@Test
public void Test2(){
SqlSession sqlSession =Utils.openSession();
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer coustomer = mapper.findCoustomerById(1);
System.out.println(coustomer);
sqlSession.close();
}
}
selectOne和selectList
动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定
如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。