MyBatis
主流的ORM框架,之前叫做iBatis,后来更名为MyBatis,实现数据持久化的框架。
同时java,.NET,Ruby三种语言,MyBatis是一个对JDBC进行封装的框架。
Hibernate是一个全自动化ORM框架,MyBatis是一个半自动化的ORM框架。
全自动化:开发者只需要调用相关接口就可以完成操作,整个流程框架都已经进行了封装。Hibernate实现了POJO和数据库之间的映射,同时可以自动生成SQL语句并完成执行。
半自动化:框架只提供一部分功能,剩下的工作仍需要开发者手动完成,MyBatis没有提供POJO与数据库表的映射,只实现了POJO与SQL之间的映射关系,需要开发者自定义SQL语句,以及数据与POJO之间的装配关系。
虽然功能没有Hibernate更加方便,但是这种半自动化的方式提高了框架的灵活性,开发者可以根据具体的业务需求,完成定制化的持久层解决方案。
MyBatis对所有的JDBC进行了封装,包括参数设置、SQL执行、结果集解析等,通过XML配置/注解的方式完成POJO与数据的映射。
简单讲,使用MyBatis进行开发,主要完成两步操作:
- 自己编写SQL
- 自己完成数据库数据与POJO的映射
MyBatis
- 极大简化了JDBC代码的开发
- 简单好用、容易上手、具有更好的灵活性
- 通过将SQL定义在XML中的方式降低程序的耦合度
- 支持动态SQL,可以根据具体业务需要灵活实现功能
MyBatis
- 相比于Hibernate,开发者需要完成更多的工作,比如定义SQL、设置POJO与数据的映射关系等
- 要求开发人员具备一定的SQL编写能力,在一些特定场景下工作量比较大
- 数据库移植性差,因为SQL依赖于底层数据库,如果要进行数据库迁移,部分SQL需要重新编写
MyBatis入门
创建Maven工程引入POM依赖
org.mybatismybatis3.4.5mysqlmysql-connector-java5.1.46
创建实体类
import lombok.Data;@Datapublic class People {private Integer id;private String name;private double money;}
配置MyBatis环境,在resources路径下创建config.xml(文件名可自定义),配置数据源信息
MyBatis开发有两种方式
- 使用原生接口
- Mapper代理实现自定义接口
使用原生接口
创建Mapper文件PeopleMapper.xml
select * from people where id = #{id}
namespace通常设置为文件所在包名+文件名
parameterType是参数数据类型
resultType是返回值数据类型
在全局配置文件 config.xml中注册PeopleMapper.xml
调用API完成操作
import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.example.entity.People;import java.io.InputStream;public class Test {public static void main(String[] args) {//加载MyBatis配置文件InputStream inputStream=Test.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);//获取SqlSessionSqlSession sqlSession=sqlSessionFactory.openSession();//调用MyBatis原生接口执行SQL语句String statement="org.example.mapper.PeopleMapper.findById";People people=sqlSession.selectOne(statement,1);System.out.println(people);sqlSession.close();}}
IDEA中无法直接读取resources路径下的XML文件,需要进行设置在pom.xml中添加
src/main/java**/*.xml
Mapper代理实现自定义接口
开发者只需要定义接口,并不需要实现接口,具体实现工作由Mapper代理结合配置文件完成。
自定义接口
import org.example.entity.People;import java.util.List;public interface PeopleRepository {public int save(People people);public int deleteById(Integer id);public int update(People people);public List findAll();}
创建PeopleMapper.xml,定义接口方法对应的SQL语句,statement标签根据SQL执行的业务可以选择select、insert、delete、update,MyBatis会自动根据规则创建PeopleRepository接口实现类代理对象。
规则如下
- PeopleMapper.xml中的namespace为接口的全限定类名(带着包名的类名)
- PeopleMapper.xml中statement的id为接口中对应的方法名
- PeopleMapper.xml中的parameterType和接口中对应方法的参数类型一致
- PeopleMapper.xml中的resultType和接口中方法返回值类型一致
配置PeopleRepository.xml
insert into people(name,money) values(#{name},#{money})delete from people where id = #{id}update people set name = #{name},money=#{money} where id=#{id}select * from people where id = #{id}select * from people
完成注册,在config.xml中配置
配置接口
import org.example.entity.People;import java.util.List;public interface PeopleRepository {public int save(People people);public int deleteById(Integer id);public int update(People people);public People findById(Integer id);public List findAll();}
实现类
public class Test2 {public static void main(String[] args) {InputStream inputStream=Test2.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();PeopleRepository peopleRepository=sqlSession.getMapper(PeopleRepository.class); People people=new People();people.setName("小明");people.setMoney(Double.parseDouble("666")); peopleRepository.save(people);sqlSession.commit();List people=peopleRepository.findAll();for(People people1:people){ System.out.println(people1);}sqlSession.close();}}
Mapper.xml常用配置
MyBatis配置文件有两种:
- 全局环境配置文件(数据源、事务管理、Mapper注册、打印SQL、惰性加载、二级缓存)
- Mapper配置文件(定义自定义接口的具体实现方案:SQL、数据与POJO的映射)
多表关联查询包括一对一、一对多、多对多
单表查询
select * from people where id = #{id}
业务:通过id查询People对象
目标表:test/people
实体类: org.example.entity.People
Mapper.xml设置相关配置逻辑,由MyBatis自动完成查询,生成POJO
statement 标签主要属性有id、parameterType、ResultType
id对应接口的方法名
parameterType 定义参数的数据类型
resultType定义查询结果的数据类型(实体类的成员变量列表必须与目标表的字段列表一致)
parameterType
支持基本数据类型、包装类、String、多参数、POJO等
- 基本数据类型,通过id查询POJO
接口方法
public People findById(int id);
xml
select * from people where id= #{id}
2.包装类
接口方法
public People findById(Integer id);
xml
select * from people where id = #{id}
- String类型
public People findByName(String name);
select * from people where name=#{name}
- 多参数
public People findByIdAndName(Integer id,String name);
select * from people where id=#{param1} and name=#{param2}
或
select * from people where id=#{arg0} and name=#{arg1}
- POJO
public int update(People people);
update people set name = #{name},money=#{money} where id=#{id}
resultType
resultType与parameterType的使用基本一致
- 基本数据类型
public int count();
select count(*) from people
2.包装类
public Integer count();
select count(*) from people
- String
public String findNameById(Integer id);
select name from people where id=#{id}
- POJO
public People findById(Integer id);
select * from people where id = #{id}
多表关联查询
实际开发中最常用的是:一对多和多对多
一对多
建表
CREATE TABLE t_classes(id int(11) PRIMARY KEY auto_increment,name VARCHAR(11) DEFAULT NULL);CREATE TABLE t_student(id INT PRIMARY KEY auto_increment,name VARCHAR(11) DEFAULT NULL,cid int(11) DEFAULT NULL,CONSTRAINT t_student_ibfk_1 FOREIGN KEY (cid) REFERENCES t_classes(id));
内关联
SELECT s.id sid,s.`name` sname,c.id cid,c.`name` cname FROM test.t_student s,test.t_classes c WHERE s.id=1 AND s.cid=c.id
创建实体类
import lombok.Data;@Datapublic class Student {private Integer id;private String name;private Classes classes;}
import lombok.Data;@Datapublic class Classes {private Integer id;private String name;}
接口StudentRepository
import com.test.entity.Student;public interface StudentRepository {public Student findById(Integer id);}
StudentRepository.xml
resultType直接将结果集与实体类进行映射,结果集的字段名与实体类的成员变量名相等则映射。
resultMap可以对结果集进行二次封装,根据需求来完成结果集数据到实体类的映射。
SELECT s.id sid,s.`name` sname,c.id cid,c.`name` cname FROM test.t_student s,test.t_classes c WHERE s.id=1 AND s.cid=c.id
调用
import com.test.Repository.StudentRepository;import com.test.entity.Student;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class Test1 {public static void main(String[] args) {InputStream inputStream=Test1.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();StudentRepository studentRepository=sqlSession.getMapper(StudentRepository.class);Student student=studentRepository.findById(1);System.out.println(student);}}
查询
SELECT c.id cid,c.name cname,s.id sid,s.name sname FROM test.t_classes c,test.t_student s WHERE c.id=1 AND c.id=c.cid
接口
import com.test.entity.Classes;public interface ClassesRepository {public Classes findById(Integer id);}
ClassRepository.xml
SELECT c.id cid,c.name cname,s.id sid,s.name sname FROM test.t_classes c,test.t_student s WHERE c.id=1 AND c.id=s.id
conllection和association的区别
conllection是将结果集封装成一个集合对象(多个目标对象)
association是将结果集封装成一个实体类的对象(一个目标对象)
collection是通过ofType设置数据类型,association是通过javaType设置数据类型。
多对多
多对多是双向的一对多关系
建表
CREATE TABLE t_account(id int(11) NOT NULL PRIMARY KEY auto_increment,name VARCHAR(11) default null);create table t_course(id int(11) NOT NULL PRIMARY KEY auto_increment,name varchar(11) DEFAULT null);CREATE TABLE account_course(id int(11) NOT NULL PRIMARY KEY auto_increment,aid int(11) DEFAULT null,cid int(11) DEFAULT null,CONSTRAINT account_course_ibfk_1 FOREIGN KEY (aid) REFERENCES t_account(id),CONSTRAINT account_course_ibfk_2 FOREIGN KEY (cid) REFERENCES t_course(id));
实体类
import lombok.Data;import java.util.List;@Datapublic class Account {private Integer id;private String name;private List courses;}
import lombok.Data;import java.util.List;@Datapublic class Course {private Integer id;private String name;private List accounts;}
AccountRepository
import org.example.entity.Account;public interface AccountRepository {public Account findById(Integer id);}
AccountRepository.xml
SELECT a.id aid,a.name aname,c.id cid,c.name cname FROM t_account a,account_course ac,t_course c WHERE a.id=1 AND a.id=ac.aid AND c.id=ac.cid;
config.xml
调用
public static void main(String[] args) {InputStream inputStream=Test2.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();AccountRepository accountRepository=sqlSession.getMapper(AccountRepository.class);Account account=accountRepository.findById(1);System.out.println(account);sqlSession.close();}
CourseRepository
import org.example.entity.Course;public interface CourseRepository {public Course findById(Integer id);}
CourseRepository.xml
SELECT a.id aid,a.name aname,c.id cid,c.name cname FROM t_account a,account_course ac,t_course c WHERE c.id=#{id} AND a.id=ac.aid AND c.id=ac.cid;
MyBatis逆向工程
MyBatis是半自动化的ORM框架,SQL需要开发者自定义,SQL需要单独定义在Mapper.xml中,与Mapper接口对应,使用MyBatis进行开发的基本配置:
- 实体类
- Mapper接口
- Mapper.xml
这种方法的缺陷是如果参与业务的表太多,每张表的业务都需要自定义SQL、创建实体类、Mapper接口,工作量较大。
MyBatis框架可以自动根据数据表帮助开发者生成实体类、Mapper接口及Mapper.xml,这就是逆向工程。
逆向工程概念
逆向工程是MyBatis提供的一种自动化配置方案,针对数据表自动生成MyBatis所需要的各种资源(实体类、Mapper接口、Mapper.xml),但是逆向工程只针对于单表,如果数据表之间有级联关系逆向工程无法自动生成级联关系。
使用逆向工程
MyBatis逆向工程组件是MyBatis Generator,简称MBG,是专为MyBatis框架制定代码自动生成解决方案,MBG可以根据数据表结构快速生成对应的实体类、Mapper接口、Mapper.xml,并且支持基本的CRUD操作,但是业务逻辑相对复杂的操作就需要开发者手动完成。
创建Maven工程,pom.xml添加相关依赖
org.mybatismybatis3.5.6mysqlmysql-connector-java5.1.46org.mybatis.generatormybatis-generator-core1.4.1
创建目标表
CREATE TABLE t_account(id INT(11) NOT NULL PRIMARY KEY auto_increment,name varchar(11),password VARCHAR(11),age int(11));
创建MBG配置文件generatorConfig.xml
- jdbcConnection配置数据库连接信息
- javaModelGenerator配置JavaBean的生成策略
- sqlMapGenerator配置SQL映射文件生成策略
- javaClientGenerator配置Mapper接口的生成策略
- table配置要逆向解析的数据表(tableName:表名,domainObjectName:实体类名)
generatorConfig.xml
创建GeneratorMain类,执行自动生成资源的代码
import org.mybatis.generator.api.MyBatisGenerator;import org.mybatis.generator.config.Configuration;import org.mybatis.generator.config.xml.ConfigurationParser;import org.mybatis.generator.internal.DefaultShellCallback;import java.io.File;import java.util.ArrayList;import java.util.List;public class GeneratorMain {public static void main(String[] args) {List warnings=new ArrayList();boolean overwrite=true;String genCig="/generatorConfig.xml";File configFile=new File(GeneratorMain.class.getResource(genCig).getFile());ConfigurationParser configurationParser=new ConfigurationParser(warnings);Configuration configuration=null;try{configuration=configurationParser.parseConfiguration(configFile);}catch (Exception e){e.printStackTrace();}DefaultShellCallback callback=new DefaultShellCallback(overwrite);MyBatisGenerator myBatisGenerator=null;try{myBatisGenerator=new MyBatisGenerator(configuration,callback,warnings);}catch (Exception e){e.printStackTrace();}try{myBatisGenerator.generate(null);}catch (Exception e){e.printStackTrace();}}}
MyBatis延迟加载
在中添加
可以显示执行的SQL语句,并延迟加载
建表
表orders
CREATE TABLE orders (id INTEGER PRIMARY KEY auto_increment,name VARCHAR(11),cid INTEGER);
表customer
CREATE TABLE customer(id INTEGER PRIMARY KEY auto_increment,name VARCHAR(11));
创建实体类
Customer
import lombok.Data;import java.util.List;@Datapublic class Customer {private Integer id;private String name;List orders;}
Order
import lombok.Data;@Datapublic class Order {private Integer id;private String name;private Customer customer;}
config.xml
CustomerRepository接口
import com.example.entity.Customer;public interface CustomerRepository {public Customer findById(Integer id);}
OrderRepository接口
import com.example.entity.Order;public interface OrderRepository {public Order findById(Integer id);}
CustomerRepository.xml
select * from customer where id = #{id}
OrderRepository.xml
select * from orders where id = #{id}
运行
import com.example.entity.Order;import com.example.repository.OrderRepository;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class Main {public static void main(String[] args) {InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();OrderRepository orderRepository=sqlSession.getMapper(OrderRepository.class);Order order= orderRepository.findById(1);System.out.println(order.getName());sqlSession.close();}}
MyBatis延迟加载机制,是实际开发中使用频率较高的一个功能,正确的使用延迟加载,可以有效减少Java Application和数据库的交互次数,从而提高整个系统的运行效率,延迟加载是为了提高程序运行效率的一种手段,一般应用于多表关联查询的业务场景。
MyBatis缓存
使用缓存的作用也是为了减少Java Application与数据库的交互次数,从而提升程序的运行效率。
MyBatis有两种缓存:一级缓存和二级缓存
一级缓存
MyBatis自带一级缓存,并且是无法关闭的,一直存在,一级缓存的数据存储在SqlSession中。
即使用同一个SqlSession进行查询操作的时候,一级缓存存在,如果使用多个SqlSession进行查询操作,一级缓存不存在,缓存只针对于查询,但是如果SqlSession执行了增、删、改操作,MyBatis会自动清空SqlSession缓存中的数据,以此来保证数据的一致性。
一级缓存不需要进行任何配置,直接使用即可。
二级缓存
MyBatis二级缓存是比一级缓存作用域更大的缓存机制,它是Mapper级别的,只要是同一个Mapper无论使用多少个SqlSession,数据都是共享的。
MyBatis二级缓存默认是关闭的,需要使用时可以通过配置手动开启
实体类
import lombok.Data;@Datapublic class MyClass {private Integer id;private String name;}
Mapper接口
import com.example.entity.MyClass;public interface MyClassRepository {public MyClass findById(Integer id);}
Mapper.xml
select * from t_classes where id = #{id}
测试
import com.example.entity.MyClass;import com.example.repository.MyClassRepository;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class Main {public static void main(String[] args) {InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();MyClassRepository myClassRepository=sqlSession.getMapper(MyClassRepository.class);MyClass myClass=myClassRepository.findById(1);System.out.println(myClass);MyClass myClass2=myClassRepository.findById(1);System.out.println(myClass2);sqlSession.close();}}
触发一级缓存,只查询一次数据库。
MyBatis可以使用自带的二级缓存,也可以使用第三方ehcache二级缓存
自带二级缓存
config.xml中配置开启二级缓存
在Mapper.xml中配置二级缓存
select * from t_classes where id = #{id}
实体类实现Serializable接口
import lombok.Data;import java.io.Serializable;@Datapublic class MyClass implements Serializable {private Integer id;private String name;}
调用
import com.example.entity.MyClass;import com.example.repository.MyClassRepository;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class Main {public static void main(String[] args) {InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();MyClassRepository myClassRepository=sqlSession.getMapper(MyClassRepository.class);MyClass myClass=myClassRepository.findById(1);System.out.println(myClass);sqlSession=sqlSessionFactory.openSession();myClassRepository=sqlSession.getMapper(MyClassRepository.class);MyClass myClass3=myClassRepository.findById(1);System.out.println(myClass3);sqlSession.close();}
第三方ehcache二级缓存
pom.xml
net.sf.ehcacheehcache-core2.4.3org.mybatismybatis-ehcache1.0.0
resources路径下创建ehcache.xml
config.xml中配置二级缓存
Mapper.xml中配置二级缓存
select * from t_classes where id = #{id}
实体类不需要实现序列化接口
MyBatis动态SQL
实体类
public class User{private Integer id;private String username;private String password;private Integer age;}
- 通过id和username查询User
- 通过username和password查询User
- 通过password和age查询User
UserRepository.xml
select * from t_user where id =#{id} and username=#{username}select * from t_user where username=#{username} and password=#{password}select * from t_user where password=#{password} and age=#{age}
MyBatis动态SQL,SQL不是固定的,可以根据不同的参数信息来动态拼接不同的SQL,以适应不同的需求。
实体类
import lombok.Data;@Datapublic class User {private Integer id;private String username;private String password;private Integer age;}
Mapper.java
import com.example.entity.User;public interface UserRepository {public User findByUser(User user);}
- if
- where
- choose、when
Mapper.xml
select * from t_userid=#{id}and username = #{username}and password = #{password}and age = #{age}
用choose、when
select * from t_userid=#{id}and username = #{username}and password = #{password}and age = #{age}
- trim
通过设置prefix和suffix参数来完成使用的
select * from t_userid=#{id}and username = #{username}and password = #{password}and age = #{age}
- set
set标签用于Update操作,会自动根据参数生成SQL语句
Mapper.java
import com.example.entity.User;public interface UserRepository {public int update(User users);}
Mapper.xml
update t_userusername = #{username},password = #{password},age = #{age}where id = #{id}
调用
import com.example.entity.User;import com.example.repository.UserRepository;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;public class Main {public static void main(String[] args) {InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession=sqlSessionFactory.openSession();UserRepository userRepository=sqlSession.getMapper(UserRepository.class);User user=new User();user.setId(1);user.setUsername("tom");user.setAge(3);userRepository.update(user);sqlSession.commit();sqlSession.close();}}