目录 [TOC]
MyBatis
Mybatis是一个持久层框架,使用java编写,它封装了JDBC的很多的细节,使开发者只关注sql语句本身,而不用关注使用原生jdbc时应该具有的注册驱动,创建连接,关闭连接等繁杂的过程,它使用了ORM的思想实现了结果集的封装
ORM: Object Relational Mapping 对象关系映射
就是把数据库表的字段和实体类属性对应起来,从而对实体类进行操作就可以完成对数据库表的操作
MyBatis环境搭建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.1</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.19</version > </dependency > <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.xml</include > </includes > </resource > </resources > </build >
在resources下创建MyBatis全局配置文件 mybatis-config.xml 1 2 3 4 <?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" >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <configuration > <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="cacheEnabled" value ="true" /> </settings > <properties resource ="JDBCConfiguration.properties" /> <environments default ="development" > <environment id ="development" > <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 > <mappers > <mapper resource ="mappers/AccountRepository.xml" /> </mappers > </configuration >
mapper文件约束 1 2 <?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">
MyBatis基于实现类的方式实现查询(不常用) 1 2 3 public interface AccountRepository { List<Account> findAll () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class AccountRepositoryImpl implements AccountRepository { private SqlSessionFactory factory = null ; public AccountRepositoryImpl (SqlSessionFactory factory) { this .factory = factory; } public List<Account> read () { SqlSession sqlSession = factory.openSession(); String statement = "cn.qingweico.mappers.AccountMapper.readAll" " List<Account> account = sqlSession.selectList(); sqlSession.close(); return account; } }
使用实现类的方式时映射配置文件中的id可以不用和方法名一样
1 2 3 4 5 6 7 8 9 10 11 <?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.qingweico.mappers.AccountMapper" > <cache /> <select id ="readAll" resultType ="cn.qingweico.entity.Account" > select * from account; </select > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class BasedOnTheClass { public static void main (String[] args) { InputStream inputStream = BasedOnTheClass.class.getClassLoader().getResourceAsStream("mybatis-config.xml" ); InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); AccountRepository accountRepository = new AccountRepositoryImpl (sqlSessionFactory); List<Account> list = accountRepository.findAll(); for (Account account : list){ System.out.println(account); } } }
1 2 3 4 <mappers > <mapper resource ="cn/qingweico/mappers/AccountMapper.xml" /> </mappers >
MyBatis基于接口(Mapper代理)的方式实现查询 1 2 3 4 5 6 7 <mapper namespace ="cn.qingwei.repository.AccountRepository" > <select id ="read" resultType ="cn.qingweico.entity.Account" > select * from account; </select > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Demo { public static void main (String[] args) { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); AccountRepository accountRepository = sqlSession.getMapper(AccountRepository.class); List<Account> list = accountRepository.read(); for (Account account : list){ System.out.println(account); }
使用接口实现查询时映射文件中id的名称必须和接口中的方法名保持一致
Myatis cud语句 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <insert id ="insert" parameterType ="cn.qingweico.entity.Account" > <selectKey keyProperty ="id" resultType ="int" keyColumn ="id" order ="AFTER" > select last_insert_id(); </selectKey > insert into account values (#{username},#{password }); </insert > <delete id ="delete" parameterType ="int" > delete from account where id = #{id}; </delete > <select id ="findById" parameterType ="String" resultType ="cn.qingweico.entity.Account" > select * from account where username like '%${value}%'; </select >
typeAliases标签 1 2 3 4 5 6 7 8 9 <typeAliases > <typeAlias type ="cn.qingweico.entity.Student" alias ="Student" /> </typeAliases > <typeAliases > <package name ="cn.qingweico.entity" /> </typeAliases >
数据库中字段名可以和实体类中属性名称不一样 1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="studentMap" type ="Student" > <id column ="id" property ="id" /> <result column ="name" property ="name" /> <association property ="classes" javaType ="Class" > <result column ="class_id" property ="classId" /> <result column ="class_name" property ="className" /> </association > </resultMap >
起别名的作用是可以简写resultType中的值(接口的返回类型) 并不是可简写namespace中的值(mapper文件映射的接口所在的位置),不要混为一谈
连接池 可以减少获取连接所消耗的次数
一个存放很多Connection的集合容器,且线程安全(保证两个线程不能拿到同一个连接)
实现的队列的性质(先进先出)
type类型包括POOLED UNPOOLED JNDI
POOLED :每次使用时都会从连接池中获取连接,使用完后会放入连接池
UNPOOLED:不使用连接池,每次使用都会加载驱动,创建连接
MyBatis中的事务 1 SqlSession sqlSession = sqlSessionFactory.openSession(true );
动态SQL 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Student implements Serializable { private Integer id; private String name; private Class classes; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Class implements Serializable { private Integer classId; private String className; private List<Student> students; }
if标签 1 2 3 4 5 6 7 8 9 10 11 12 13 <select id ="findByStudent" resultType ="Student" > select * from student where <if test ="id != null" > id = #{id} </if > <if test ="name != null" > and name = #{name} </if > <if test ="classes != null" > and class_id = #{classes.classId} </if > </select >
where标签 当id为null时,where会直接和and连接,where标签可以解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 <select id ="findByStudent" parameterType ="Student" resultType ="Student" > select * from student where 1 = 1 <if test ="id != null" > and id = #{id} </if > <if test ="name != null" > and name = #{name} </if > <if test ="classes != null" > and class_id = #{classes.classId} </if > </select >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="findByStudent" parameterType ="Student" resultType ="Student" > <where > <if test ="id != null" > id = #{id} </if > <if test ="name != null" > and name = #{name} </if > <if test ="classes != null" > and class_id = #{classes.classId} </if > </where > </select >
choose标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="findByStudent" parameterType ="Student" resultType ="Student" > select * from student <where > <choose > <when test ="id !=null" > id = #{id} </when > <when test ="name != null" > name = #{name} </when > <when test ="classes != null" > class_id = #{classes.classId} </when > </choose > </where > </select >
trim标签 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 select * from student <trim prefix ="where" prefixOverrides ="and" > <if test ="id != null" > id = #{id} </if > <if test ="name != null" > and name = #{name} </if > <if test ="classes != null" > and class_id = #{classes.classId} </if > </trim >
set标签 1 2 3 4 5 6 7 8 9 10 11 12 13 <update id ="update" parameterType ="Student" > update student <set > <if test ="name != null" > name = #{name}, </if > <if test ="classes != null" > class_id = #{classes.classId} </if > </set > where id = #{id} </update >
foreach标签 可以一次性生成很多值,主要用于sql的in语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Student implements Serializable { private Integer id; private String name; private Class classes; private List<Integer> ids;
1 2 3 4 5 6 7 8 9 10 <select id ="studentList" resultType ="Student" parameterMap ="Student" > select * from student <where > <foreach collection ="ids" open ="id in (" close =")" item ="id" separator ="," > #{id} </foreach > </where > </select >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void ForEachLabel () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class); Student student = new Student (); List<Integer> list = new ArrayList <>(); list.add(1 ); list.add(2 ); list.add(3 ); list.add(4 ); student.setIds(list); System.out.println(studentRepository.studentList(student)); }
MyBatis一对一操作查询 需求:查询学生信息时要求把所在班级的信息查询出来(级联查询)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <resultMap id ="studentMap" type ="Student" > <id column ="id" property ="id" /> <result column ="name" property ="name" /> <association property ="classes" javaType ="Class" > <result column ="class_id" property ="classId" /> <result column ="class_name" property ="className" /> </association > </resultMap > <select id ="findById" resultMap ="studentMap" > select id, `name`, c.class_id, c.class_name from student s, tb_class c where s.class_id = c.class_id and s.id = #{id}; </select >
MyBatis一对多查询操作 需求:查询班级信息,要求把所在该班级的所有学生查出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Class implements Serializable { private String classId; private String className; private List<Student> students; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <resultMap id ="classMap" type ="Class" > <id column ="class_id" property ="classId" /> <result column ="class_name" property ="className" /> <collection property ="students" ofType ="student" > <result column ="id" property ="id" /> <result column ="name" property ="name" /> </collection > </resultMap > <select id ="findById" resultMap ="classMap" > select id, `name`, c.class_id, c.class_name from student s, tb_class c where s.class_id = c.class_id and c.class_id = #{id}; </select >
MyBatis多对多查询操作 需求:查询客户,要求把该客户购买的货物查询出来
需求:查询货物,要求把购买该货物的客户信息查询出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Customer { private Integer customerId; private String customerName; private List<Goods> goodsList; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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.qingweico.repository.CustomerRepository" > <resultMap id ="customerMap" type ="Customer" > <id column ="customer_id" property ="customerId" /> <result column ="customer_name" property ="customerName" /> <collection property ="goodsList" ofType ="Goods" > <result column ="goods_id" property ="goodsId" /> <result column ="goods_name" property ="goodsName" /> </collection > </resultMap > <select id ="findGoodsListByCustomerId" resultMap ="customerMap" > select c.*, g.goods_id, g.goods_name from customer c, goods g, customer_goods cg where cg.goods_id = g.goods_id and c.customer_id = cg.customer_id and c.customer_id = #{customer_id}; </select > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Goods { private Integer goodsId; private String goodsName; private List<Customer> customersList; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?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.qingweico.repository.GoodsRepository" > <resultMap id ="goodsMap" type ="Goods" > <id column ="goods_id" property ="goodsId" /> <result column ="goods_name" property ="goodsName" /> <collection property ="customerList" ofType ="Customer" > <result column ="customer_id" property ="customerId" /> <result column ="customer_name" property ="customerName" /> </collection > </resultMap > <select id ="findCustomerListByGoodsId" resultMap ="goodsMap" > select g.goods_id, g.goods_name, c.customer_id, c.customer_name from customer c, goods g, customer_goods cg where c.customer_id = cg.customer_id and cg.goods_id = g.goods_id and g.goods_id = #{goodsId}; </select > </mapper >
MyBatis延迟加载 延迟加载可以根据特定的场景来选择需要查询的表,可以减少与数据库的交互次数,从而达到提高程序的执行效率
将多表的查询分成多个单表查询
StudentRepository.xml
1 2 3 4 5 6 7 8 9 <resultMap id ="delayMap" type ="Student" > <id column ="id" property ="id" /> <result column ="name" property ="name" /> <association property ="classes" javaType ="cn.qingweico.entity.Class" select ="cn.qingweico.repository.ClassRepository.delay" column ="classes" /> </resultMap > <select id ="delay" resultMap ="delayMap" > select * from student where id = #{id}; </select >
ClassRepository.xml
1 2 3 <select id ="delay" resultMap ="classMap" > select * from tb_class where class_id = #{class_id}; </select >
在主配置文件中开启延迟加载
1 2 3 4 <settings > <setting name ="lazyLoadingEnabled" value ="true" /> </settings >
1 2 3 4 5 6 7 8 9 10 @Test public void lazyLoading () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class); System.out.println(studentRepository.delay(4 )); }
1 2 3 4 5 6 7 8 9 10 11 ==> Preparing: select * from student where id = ?; ==> Parameters: 4(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 <== Total: 1 ==> Preparing: select * from tb_class where class_id = ?; ==> Parameters: 2(Integer) <== Columns: class_id, class_name <== Row: 2, 2班 <== Total: 1 Student{id=4, name='小明', classes=Class{classId='2', className='2班', students=[]}}
1 2 3 4 5 6 7 8 9 10 @Test public void lazyLoading () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class); System.out.println(studentRepository.delay(4 ).getName()); }
1 2 3 4 5 6 ==> Preparing: select * from student where id = ?; ==> Parameters: 4(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 <== Total: 1 小明
MyBatis缓存
MyBatis自带一级缓存 默认开启且不能关闭, 作用范围是同一个sqlSession。当从同一个sqlSession获取相同的数据时,则会使用缓存。当有cud操作时,会自动清除二级缓存,保证数据的准确性
MyBatis自带的二级缓存,需要手动打开 作用范围是Mapper下的同一个namespace,二级缓存比一级缓存有更大的范围
二级缓存开启的条件
1 2 3 4 5 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Student implements Serializable { private Integer id; private String name; private Class classes; }
MyBatis注解开发 基于注解的方式(修改mybatis-config.xml配置文件中mapper注册的方式) 1 2 3 4 5 <mapper class ="cn.qingweico.repository.AccountRepository" /> <package name ="cn.qingweico.repository" />
1 2 3 4 public interface AccountRepository { @Select("select * from account") List<Account> read () ; }
Mybatis建立实体类属性和数据库字段的关系映射 当实体类中的属性名可以和数据库中的字段名不一样时:
1 2 3 4 5 @Results(id = "studentMap",value = { /*id为true表示主键 property表示实体类属性的值,column表示数据库字段名*/ @Result( id = true,property = "id",column = "id"), @Result(property = "name",column = "name"), })
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Student implements Serializable { private Integer id; private String name; private Class classes; }
1 2 3 4 @Select("select * from student") @ResultMap(value = "studentMap") List< Student> findAll();
MyBatis建立一对一的查询(立即加载) 1 2 3 4 5 6 7 8 9 @Select("select * from student where id = #{id}") @Results(id = "studentMap", value = { @Result(id = true, property = "id", column = "id"), @Result(property = "name", column = "name"), //fetchType表示加载的方式 eager表示立即加载 lazy表示延迟加载 //在一对一中使用立即加载 在多对一中使用延迟加载 @Result(property = "classes", column = "class_id",one = @One(select = "cn.qingweico.repository.ClassRepository.findById", fetchType = FetchType.EAGER)) }) Student findById (Integer id) ;
1 2 3 4 5 6 7 8 @Select("select * from tb_class where class_id = #{class_id}") @Results(id = "classMap", value = { @Result(property = "classId",column = "class_id"), @Result(property = "className",column = "class_name") }) Class findById () ;
1 2 3 4 5 6 7 8 9 10 @Test public void basedOne2OneAnnotations () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class); System.out.println(studentRepository.findById(4 )); }
1 2 3 4 5 6 7 8 9 10 11 ==> Preparing: select * from student where id = ? ==> Parameters: 4(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 ====> Preparing: select * from tb_class where class_id = ? ====> Parameters: 2(Integer) <==== Columns: class_id, class_name <==== Row: 2, 2班 <==== Total: 1 <== Total: 1 Student{id=4, name='小明', classes=Class{classId='2', className='2班', students=null}}
1 2 3 4 5 6 7 8 9 10 @Test public void basedOne2OneAnnotations () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class); System.out.println(studentRepository.findById(4 ).getName()); }
1 2 3 4 5 6 7 8 9 10 11 ==> Preparing: select * from student where id = ? ==> Parameters: 4(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 ====> Preparing: select * from tb_class where class_id = ? ====> Parameters: 2(Integer) <==== Columns: class_id, class_name <==== Row: 2, 2班 <==== Total: 1 <== Total: 1 小明
将FetchType.EAGER
换为FetchType.LAZY
再试一次
1 2 3 4 5 6 7 ==> Preparing: select * from student where id = ? ==> Parameters: 4(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 <== Total: 1 小明
MyBatis建立一对多查询 1 2 3 4 5 6 7 @Select("select * from tb_class where class_id = #{class_id}") @Results(id = "classMap", value = { @Result(property = "classId",column = "class_id"), @Result(property = "className",column = "class_name"), @Result(property = "students", column ="class_id" ,many = @Many(select ="cn.qingweico.repository.StudentRepository.findStudentByClassId",fetchType = FetchType.LAZY)) }) Class findStudentListByClassId (int class_id) ;
1 2 3 @Select("select * from student where class_id = #{class_id}") List<Student> findStudentByClassId ()
1 2 3 4 5 6 7 8 9 10 @Test public void basedOne2ManyAnnotations () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); ClassRepository classRepository = sqlSession.getMapper(ClassRepository.class); System.out.println(classRepository.findStudentListByClassId(2 )); }
1 2 3 4 5 6 7 8 9 10 11 12 ==> Preparing: select * from tb_class where class_id = ? ==> Parameters: 2(Integer) <== Columns: class_id, class_name <== Row: 2, 2班 <== Total: 1 ==> Preparing: select * from student where class_id = ? ==> Parameters: 2(Integer) <== Columns: id, name, class_id <== Row: 4, 小明, 2 <== Row: 7, 小达, 2 <== Total: 2 Class{classId='2', className='2班', students=[Student{id=4, name='小明', classes=null}, Student{id=7, name='小达', classes=null}]}
1 2 3 4 5 6 7 8 9 10 @Test public void basedOne2ManyAnnotations () throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); ClassRepository classRepository = sqlSession.getMapper(ClassRepository.class); System.out.println(classRepository.findStudentListByClassId(2 ).getClassName()); }
1 2 3 4 5 6 7 ==> Preparing: select * from tb_class where class_id = ? ==> Parameters: 2(Integer) <== Columns: class_id, class_name <== Row: 2, 2班 <== Total: 1 2班
MyBatis注解开启二级缓存 1 2 3 4 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
1 2 @CacheNamespace(blocking = true)