1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > MyBatis持久层框架

MyBatis持久层框架

时间:2018-08-13 22:26:57

相关推荐

MyBatis持久层框架

文章目录

MyBatis🤞🤞1. 简介2. HelloWorld3. MyBatis全局配置文件4. SQL映射文件5. 查询的返回结果6. 联合查询7. 分步查询8. 动态SQL9. 扩展:OGNL10. 缓存机制11. 整合第三方缓存

MyBatis🤞🤞

✨✨✨✨✨✨✨✨✨✨✨✨✨✨

1. 简介

(1)简介

什么是MyBatis?

MyBatis 是一款优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的实体类POJO,普通的 Java对象】映射成数据库中的记录。MyBatis 本是apache的一个开源项目ibatis, 这个项目由apache 迁移到了google code,并且改名为MyBatis 。Mybatis官方文档 : /mybatis-3/zh/index.htmlGitHub : /mybatis/mybatis-3

为什么需要MyBatis?

Mybatis就是帮助我们将数据存入数据库中,和从数据库中取数据。传统的jdbc操作,有很多重复代码块 。比如 : 数据取出时的封装 , 数据库的建立连接等等… , 通过框架可以减少重复代码,提高开发效率。MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射。所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!

MyBatis的优点:

简单易学。 本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以。 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离。 使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。 提供xml标签,支持编写动态sql。

什么是持久化?

持久化是将程序数据在持久状态和瞬时状态间转换的机制。即把内存中的数据保存到可永久保存的存储设备中(如磁盘)。JDBC就是一种持久化机制。文件IO也是一种持久化机制。为什么需要持久化服务呢?那是由于内存本身的缺陷引起的 内存断电后数据会丢失。内存过于昂贵,与硬盘、光盘等外存相比,内存的价格要高2~3个数量级,而且维持成本也高,至少需要一直供电吧。

Hibernate 简介

数据库交互框架(ORM框架)。ORM—> Object Relation Mapping 对象关系映射。黑箱操作,不需写SQL。Hibernate 缺点: 不能自己写SQL。全映射框架,做部分字段映射很难。

MyBatis将SQL写在配置文件中。

即:

MyBatis将重要步骤抽取出来,可以定制,其他步骤自动化,重要的步骤都写在配置文件中(易于修改)完全解决数据库优化的问题MyBatis底层就是对原生JDBC的一个简单封装。半持久化框架。既将java编码与SQL抽取分离,还不会失去自动化功能。MyBatis是一个轻量级框架

SQL和java分开,功能边界清晰,一个专注于数据,一个专注于业务。

(2)环境

数据库驱动:mysql-connector-java

MyBatis包:org.mybatis

日志包(非必需):在关键环节就会有日志打印。:log4j

这个日志框架依赖一个 log4j.xml 的配置文件。 (放在类路径下)

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j="/log4j/"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8" /><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /></layout></appender><logger name="java.sql"><level value="debug" /></logger><logger name="org.apache.ibatis"><level value="info" /></logger><root><level value="debug" /><appender-ref ref="STDOUT" /></root></log4j:configuration>

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>

2. HelloWorld

(1)环境搭建

创建java工程

创建测试数据库

创建表。JavaBean,操作数据库的dao接口

(2)MyBatis操作数据库的一般步骤

导包写配置写测试

配置文件:两个

全局配置文件:指导MyBatis如何正确运行,例如:连接哪个数据库 在<configuration>下配置环境<environments default="development">。环境可以配置多个。<environment>配置基本属性。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-////DTD Config 3.0//EN""/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_test?serverTimezone=UTC&amp;rewriteBatchedStatements=true"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="lu/dao/EmpDao.xml"/></mappers></configuration>

SQL映射文件:每一个方法都如何想数据库发送SQL语句,如何执行等。 将namespace为接口的全类名。相当于告诉MyBatis这个配置文件是实现哪个接口的使用<select>标签定义一个查询操作 id:Dao接口中的方法名resultType:指定方法运行后的返回值类型(查询操作必须指定)在SQL语句中可以使用#{id}取值。取的是方法传过来的参数。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><!--映射,namespace;名称空间,写接口全类名,相当于告诉MyBatis这个配置文件是实现哪个接口的--><mapper namespace="lu.dao.EmpDao"><!-- select 标签定义一个查询操作。id:方法名。resultType:指定方法运行后的返回值类型(查询操作必须指定)#{id}:代表取方法传过来的参数的名--><select id="getEmpById" resultType="lu.pojo.Emp">select * from emp where id = #{id}</select></mapper>

我们写的dao接口的实现文件,MyBatis默认是不知道的,需要在全局配置文件中注册。使用<mappers>标签

<mappers><mapper resource="lu/dao/EmpDao.xml"/></mappers>

从 XML 中构建 SqlSessionFactory,SqlSessionFactory构建SqlSession(SQL会话,SqlSession是跟数据库的一次会话,就相当于是一个Connection)。

String resource = "org/mybatis/example/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

测试:

根据全局配置文件先创建一个:SqlSessionFactory从SqlSessionFactory中获取SqlSession对象来操作数据库用SqlSession拿到dao接口的实现使用dao接口的实现去调用方法,去查询。

import lu.dao.EmpDao;import lu.pojo.Emp;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 java.io.IOException;import java.io.InputStream;/*** @author 满眼星河* @create -11-25-9:05*/public class MyTest {@Testpublic void test01() throws IOException{//1. 根据全局配置文件构建出一个SQLSessionFactory/*** SqlSessionFactory是SqlSession工厂,负责创建SqlSession对象。** SqlSession:代表和数据库的一次会话。*/String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession=null;Emp emp=null;try{//2. 获取和数据库的一次会话,相当于getConnection()。拿到连接sqlSession = sqlSessionFactory.openSession();//3. 使用SQLSession操作数据库,获取到dao接口的实现。(相当于获取EmpDao.xml)EmpDao empDao = sqlSession.getMapper(EmpDao.class);//4. 调用dao接口的方法去查询即可emp = empDao.getEmpById(1);}catch (Exception e){e.printStackTrace();}finally{sqlSession.close();}System.out.println(emp);}}

配置文件的约束文件:dtd是约束文件,

/dtd/mybatis-3-config.dtd

MyBatis中的增删改不会自动提交。

解决方案一:需要手动提交。

mit();

解决方案二:在获取sqlsession时传入true参数,表示设置为自动提交。

SqlSession sqlSession = sqlSessionFactory.openSession(true);

3. MyBatis全局配置文件

(1)两个文件:

一个是全局配置文件:mybatis-config.xml 指导MyBatis正确运行的一些全局配置文件。 SQL映射文件:EmpDao.xml 相当于是对EmpDao接口的实现。

//class com.sun.proxy.$Proxy6EmpDao empDao = sqlSession.getMapper(EmpDao.class);System.out.println(empDao.getClass());

EmpDao是一个代理对象,MyBatis自动为其创建一个代理对象。

HelloWorld细节:

获取到的是接口的代理对象。SqlSessionFactory和SqlSession SqlSessionFactory是创建SqlSession的工厂。SqlSession:相当于Connection。和数据库的一次会话。

(2)全局配置文件:

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-////DTD Config 3.0//EN""/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_test?serverTimezone=UTC&amp;rewriteBatchedStatements=true"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="lu/dao/EmpDao.xml"/></mappers></configuration>

配置文件中可以写如下配置:

这些标签的编写都是有顺序的,而且必须遵守,否则报错。

properties属性:引入外部资源文件。在下面可以使用${}获取值。

<!--1. 和Spring的Context的:property-placeholder:引用外部配置文件--><!--resource:从类路径下引入资源url:可以引用磁盘文件或网络资源--><properties resource="dbconfig.properties"></properties><environments default="development"><environment id="development"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="${driverclass}"/><property name="url" value="${jdbcurl}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment></environments>

setting设置:

MyBatis中查询操作,会将查询结果自动封装为一个指定的对象(前提是要求数据库字段和pojo类的属性名一致)

如果不一致,我们以前采取的措施是在SQL语句中给相应字段起别名。现在我们可以在MyBatis的配置文件中使用setting进行设置。数据库中的“-”连接命名转为java中的驼峰命名。 emp_Name ----> empName 数据库不区分大小写。

<settings><setting name="mapUnderscoreToCamelCase" value="true"/></settings>

typeAliases属性:指定别名。指定之后就可以在Empdao的实现文件中使用。

是package批量指定别名,默认就是类名。可以是java类上使用注解:@Alias(“Empss”)指定别名。推荐不起别名,就使用全类名。

<typeAliases><package name="lu.pojo" /></typeAliases>

<typeAliases><!-- 为一个java类其别名,默认是类名(不区分大小写),可以使用alias指定--><typeAlias type="lu.pojo.Emp" alias="Emp"/></typeAliases>

已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,需要注意的是由基本类型名称重复导致的特殊处理。

typeHandlers:类型处理器。

作用:无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。(部分)

objectFactory:对象工厂。查询SQL的返回的对象。MyBatis底层利用objectFactory通过反射来给我们创建对象。

plugins:插件。是MyBatis中的一个强大的功能。

MyBatis通过四大对象来工作。 Executor:执行器,执行CRUD操作。ParameterHandler:参数处理器,给预编译对象设置参数。ResultSetHandler:结果集处理器。负责将查询出来的结果集封装成对应的javaBean对象。StatementHandler:预编译参数。相当于原生的PreparedStatement。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

environments:配置环境。可以配置多个环境,

environment:配置一个具体的环境。(需要一个事务管理器和一个数据源) transactionManagerdataSource id是一个环境的唯一辨识,要切换使用的环境,在<environments default="development">中指定即可。我们以后的数据源和事务管理都是spring来管理。

<environment id="development"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="${driverclass}"/><property name="url" value="${jdbcurl}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment>

databaseIdProvider:MyBatis用来做数据库移植性的。type="DB_VENDOR"这是写死的。 name:数据库厂商别名。value:给这个标识起个别名 MySQL,SQL Server

<databaseIdProvider type="DB_VENDOR"><property name="MySQL" value="mysql"/><property name="SQL Server" value="sqlserver"/><property name="Oracle" value="oracle"/></databaseIdProvider>

精确匹配优先。

<!-- 默认这个查询不区分环境的,使用databaseId指定具体的数据库--><select id="getEmpById" resultType="Empss" databaseId="mysql">select * from emp where id = #{id}</select>

mapper标签:将写好的SQL映射文件需要使用mapper注册进来。 resource:在类路径下找SQL映射文件class:直接引用接口的全类名。必须将SQL映射文件放在和接口同包下,而且必须同名。另外一种用法:使用注解开发url:从磁盘路径下,或者网络路径下引入

<mappers><mapper resource="lu/dao/EmpDao.xml"></mapper><mapper class="lu.dao.EmpDaoAnnotation"></mapper><mapper url=""></mapper></mappers>

批量注册:

name写Dao接口所在的包名。

注意:批量注册时,dao的实现文件(例如:EmpDao.xml)一定要放在和dao同包的路径下才能获取到。

注解:

​ 使用@Select之类的注解

public interface EmpDaoAnnotation {@Select("select * from emp where id = #{id}")Emp getEmpById(Integer id);}

注解的形式只能使用class注册。

<mappers><mapper class="lu.dao.EmpDaoAnnotation"></mapper></mappers>

注解和配置配合使用:

重要的dao写配置。简单的dao就直接写注解。

4. SQL映射文件

(1)Dao映射文件中能写的标签:

cache:缓存相关cache-ref:缓存相关delete,update,insert,select:和增删改查有关resultMap:结果映射,自定义结果集的封装映射规则。SQL:抽取可重用的SQL语句。

select标签的属性

id:指定方法。不能写重载方法。

statementType:指定执行SQL语句的执行器类型。

statement:相当于原生JDBC的statementprepareStatement:相当于原生JDBC的prepareStatementCallable:调用数据库的存储过程

实现获取自增主键:

<!-- 让MyBatis自动的将自增的id赋值给传入的Emp对象的id属性--><!--useGeneratedKeys="true" :相当于告诉MyBatis执行完SQL语句之后,再调用JDBC的getGeneratedKeys,拿到自增的主键。keyProperty="id" : 将自增的属性赋值给Emp对象中的id属性。--><insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">insert into Emp(name,deptId) values(#{name},#{deptId})</insert>

但是:有的数据库不支持自增主键。或者没有设置主键自增。

在使用全字段更新时,如果插入的id已有,就会报错,为了解决这个问题,就可以使用selectKey先查询出最大的id再加一,赋值JavaBean给指定的属性。

调用这个方法时,传入的Emp为:

Emp emp = new Emp(null, "BBB", 2);int i = empdao.insertEmp(emp);

<insert id="insertEmp"><selectKey order="BEFORE" resultType="integer" keyProperty="id">select Max(id)+1 from emp</selectKey>insert into Emp(id,name,deptId) values(#{id},#{name},#{deptId})</insert>

selectKey标签的属性。

(2)查询

传参到底能传什么?

<select id="getEmpByIdAndName" resultType="nuc.pojo.Emp">select * from emp where id=#{id} and name=#{name}</select>

多个参数时,#{} 取不出来。报错如下:

Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]

这样才是正确的:或者使用 param1, param2

<select id="getEmpByIdAndName" resultType="nuc.pojo.Emp">select * from emp where id=#{arg0} and name=#{arg1}</select>

如果只有一个参数时,在SQL语句中取参数时,使用#{} ,大括号中无论写什么都能正确获取到参数。

现象:

单个参数:

基本类型: 取值:#{随便写} 复杂类型:

多个参数:(推荐在方法命名时使用@Param命名参数)

取值:#{参数名}是无效的。只能使用#{param1……}

原因:只要传入的多个参数,MyBatis会自动将这些参数封装在一个Map中。使用的key就是参数的索引和第几个标识。例:map.put(“param1”,传入的参数值)所以:#{key} 就是去这个map中取值。

我们可以告诉MyBatis,封装map的时候,使用我们指定的key。使用 @Param 注解

Emp getEmpByIdAndName(@Param("id") Integer id, @Param("name") String name);

所以,@Param 为参数指定key。命名参数。

传入pojo:

取值:#{pojo的属性名}

传入Map:可以直接传入一个Map,将要使用的参数封装起来。

Emp getEmpByMap(Map<String,Object> map);

所以,#{}的本意就是去Map中取值。

EmpDao接口:

package nuc.inter;public interface EmpDao {Emp getEmpByMap(Map<String,Object> map);}

EmpDao.xml实现文件:

<select id="getEmpByMap" resultType="nuc.pojo.Emp">select * from emp where id=#{testId} and name=#{testName}</select>

传入Map作为参数的测试:

@Testpublic void test01(){SqlSession sqlSession = sqlSessionFactory.openSession();EmpDao empdao = sqlSession.getMapper(EmpDao.class);Map<String, Object> map = new HashMap<>();map.put("testId",2);map.put("testName","李四");Emp emp = empdao.getEmpByMap(map);System.out.println(emp);}

多个参数的情况下:MyBatis会自动封装Map。

扩展:

Dao接口中的方法:

Emp method(@Param("id") Integer id, @Param("name") String name, @Param("emp") Emp emp);

取值:

#{id}#{name}#{emp.name} 取这个对象的name属性。

传参推荐使用Map。

在MyBatis中有两种取值方式:

#{}:是参数预编译的方式,参数的位置都是?,参数后来都是预编译设置进去的。

${}:不是预编译方式,而是直接和SQL进行拼串。

使用场景:SQL语句只要参数位置是支持预编译的。可以动态传入表名

select * from ${tableName} where id=${testId} and name=#{testName}

一般都使用#{},在不支持参数预编译的位置要使用${}。

5. 查询的返回结果

(1)查询多条记录,返回一个List

List getAllEmp();

resultType:如果返回的是集合,这里写集合里面的元素的类型MyBatis会自动给我们封装为一个List

<!-- --><select id="getAllEmp" resultType="nuc.pojo.Emp">select * from emp</select>

(2)查询单条记录,返回类型写Map

列名作为key,值作为value

Map<String,Object> getEmpByIdReturnMap(Integer id);

(3)查询多条记录,封装为Map。

在Dao接口的方法上使用注解:@MapKey(value="") 指定key多条记录时,select标签的返回值类型写Map中元素的类型。

@MapKey("id")Map<Integer,Emp> getAllEmpReturnMap();

<select id="getAllEmpReturnMap" resultType="nuc.pojo.Emp">select * from emp</select>

(4)自定义封装规则

MyBatis默认自动封装结果集:

按照列名和属性名一一对应的规则。(不区分大小写)

如果不一一对应

开启驼峰命名法。满足驼峰命名规则 数据库中是:aaa_BBBJavaBean中是:aaaBBB 不满足驼峰命名规则,就起别名。自定义结果集:哪一列和那个JavaBean的属性对应,我们自己定义。

自定义封装:

resultType:是使用默认规则,属性和列名一一对应。resultMap:resultMap="myEmp"查出数据封装结果的时候,使用myEmp自定义的规则封装 。使用<resultMap>标签自定义封装规则: id属性:唯一标识,让别名在后面可以使用。type:指定为哪个JavaBean自定义封装规则。写JavaBean的全类名再使用<id>标签指定主键的对应规则 column:指定哪一列是主键列。property:指定JavaBean中哪个属性封装id这一列数据。 使用<result>标签定义普通列的对应规则。

自定义结果集代码示例:

<select id="getAllEmpReturnMap" resultMap="myEmp">select * from emp</select><!-- resultMap:自定义结果集。id:唯一标识,让别名在后面可以使用type:指定为哪个JavaBean自定义封装规则。写JavaBean的全类名--><resultMap id="myEmp" type="nuc.pojo.Emp"><!-- id:用来指定主键列的对应规则column:指定哪一列是主键列property:指定JavaBean中哪个属性封装id这一列数据--><id column="id" property="id"></id><!-- 普通列对应property:JavaBean的属性column:对应数据库中哪一列的值--><result property="name" column="Cname"></result></resultMap>

6. 联合查询

(1)如果JavaBean中还有一个JavaBean

创建数据库表,搭建环境。创建key表和lock表,一把钥匙只能开一个锁,所有在key表中建立外键,引用lock表中的数据。

(2)代码实现:

JavaBean—>Key类:

package nuc.pojo;/*** 查询钥匙的时候,顺便将这把钥匙能开的锁也查出来,封装进lock对象中。*/public class Key {private Integer id;private String keyName;/*** 表示当前这把钥匙能开哪个锁*/private Lock lock;//省略getter、setter、toString}

JavaBean—>Lock类:

package nuc.pojo;public class Lock {private Integer id;private String LockName;//省略getter、setter、toString}

KeyDao接口:

public interface KeyDao {/*** 将钥匙和锁的信息一起查询出来* @param id* @return*/Key getKeyById(Integer id);}

KeyDao.xml:

使用“左外连接查询”

SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.lockname FROM t_key k LEFT JOIN t_lock l ON k.`lockid`=l.`id` WHERE k.id=1;

在多表查询时,结果中有两个字段是同名的,为了后面的查询操作方便封装,需要在SQL中起别名。

在级联查询时,使用自定义封装:

使用<resultMap>标签: property:JavaBean中的属性column:数据库表中的值

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="nuc.inter.KeyDao"><select id="getKeyById" resultMap="myKey">SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.lockname FROM t_key kLEFT JOIN t_lock l ON k.`lockid`=l.`id`WHERE k.id=#{id};</select><!-- 自定义封装规则,使用级联属性封装查询出来的结果property:JavaBean中的属性column:数据库表中的值--><resultMap id="myKey" type="nuc.pojo.Key"><id property="id" column="id"></id><result property="keyName" column="keyname"/><result property="lock.id" column="lid"/><result property="lock.LockName" column="lockname"/></resultMap></mapper>

上面使用的时级联赋值,MyBatis中推荐使用的是association 标签

实例:

也是在resultMap标签中定义: association标签的属性: property:指定JavaBean中的那个属性。javaType;指定这个属性的类型。 然后,也是使用id标签和result标签对这个属性进行赋值。

<!-- MyBatis推荐的association 标签--><resultMap id="myKey" type="nuc.pojo.Key"><id property="id" column="id"></id><result property="keyName" column="keyname"/><!-- 下面的属性是一个对象,自定义这个对象的封装规则,association表示联合了一个对象--><!-- javaType:指定这个属性的类型--><association property="lock" javaType="nuc.pojo.Lock"><!-- 在association标签体中定义这个Lock对象如何封装--><id property="id" column="lid"/><result property="LockName" column="lockname"/></association></resultMap>

(3)一个JavaBean中的属性是一个集合。

需求:查锁子,也要查询出该锁子的所有钥匙:

Lock类:

public class Lock {private Integer id;private String LockName;/*** 查询锁子的时候,将该锁子的所有钥匙都查出来,放在这个List中*/private List<Key> keys;//省略set,get方法}

Key类:

public class Key {private Integer id;private String keyName;/*** 表示当前这把钥匙能开哪个锁*/private Lock lock;//省略set,get方法}

LockDao接口:

public interface LockDao {/*** 查询所有锁子,并将该锁子的所有钥匙都查出来*/Lock getLockById(Integer id);}

LockDao.xml:

JavaBean中的属性是一个集合。需要使用自定义封装规则,在<resultMap>中使用<collection>标签定义这个集合的封装规则。 property:指定哪个属性是集合属性ofType:指定这个集合中元素的类型

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="nuc.inter.LockDao"><select id="getLockById" resultMap="myLock">SELECT k.id kid,k.keyname,k.lockid,l.id lid,l.locknameFROM t_key kLEFT JOIN t_lock l ON k.`lockid`=l.`id`WHERE l.id=#{id}</select><resultMap id="myLock" type="nuc.pojo.Lock"><id property="id" column="lid"/><result property="LockName" column="lockname"/><!--property:指定哪个属性是集合属性ofType:指定这个集合中元素的类型--><collection property="keys" ofType="nuc.pojo.Key"><id property="id" column="kid"/><result property="keyName" column="keyname"/></collection></resultMap></mapper>

SQL查询结果:

扩展思考:

1-1:一个key开一个lock

1-n:一个lock有多个key

n-n:一个老师教多个学生,一个学生又有多个老师

问题:1-n,n-1,n-n:外键放在那个表中呢?

1-n:外键放在n的那一段。n-1:和1-n一样,只是反过来看。n-n:建一个中间表来存储对应关系

老师表:

学生表:

学生-老师关系表:

7. 分步查询

查钥匙的时候,顺便查出锁子

写两个简单的select语句,先查询出钥匙,再根据查询结果中的lockid查询出对应的锁子。

LockDao.xml中简单的select语句,根据id查询锁子。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="nuc.inter.LockDao"><select id="getLockByIdSimple" resultType="nuc.pojo.Lock">select * from t_lock where id=#{id}</select></mapper>

KeyDao.xml:

在这个简单查询中使用自定义封装规则。在自定义封装中,使用association标签说明这个属性是JavaBean,然后再使用select属性指定一个查询SQL,使用column给这个SQL传递参数。可以看到过程是执行了两条SQL语句。

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-////DTD Mapper 3.0//EN""/dtd/mybatis-3-mapper.dtd"><mapper namespace="nuc.inter.KeyDao"><select id="getKeyByIdSimple" resultMap="myKey02">select * from t_key where id=#{id}</select><resultMap id="myKey02" type="nuc.pojo.Key"><id property="id" column="id"/><result property="keyName" column="keyname"/><!-- 可以告诉MyBatis,让他自己去查询(调用一个查询)select="":指定一个查询SQL的唯一标识,MyBatis自动调用这个指定的SQL将查询出的结果封装进对应对象告诉MyBatis将哪一列的值传入进去 column="lid"--><association property="lock" select="nuc.inter.LockDao.getLockByIdSimple" column="lockid"/></resultMap></mapper>

需求:在如下的案例中

需求中通常只需要查询key的名,lock只是偶尔会用到,但是MyBatis还是会每次都查询锁子,造成了浪费。

解决:只需开启全局按需加载功能:

在MyBatis的配置文件中设置:

开启延迟加载功能:lazyLoadingEnabled 置为true。

开启属性按需加载:aggressiveLazyLoading置为false

<settings><!-- 开启延迟加载功能--><setting name="lazyLoadingEnabled" value="true"/><!-- 开启属性按需加载--><setting name="aggressiveLazyLoading" value="false"/></settings>

@Testpublic void test04(){SqlSession sqlSession = sqlSessionFactory.openSession();KeyDao keyDao = sqlSession.getMapper(KeyDao.class);Key key = keyDao.getKeyByIdSimple(1);//如果System.out.println(key.getKeyName());//解决数据库资源的浪费:按需加载--->需要的时候才去查询//只需开启全局按需加载功能}

因为代码中只需要key的name,所有就只执行一条SQL,只查询key的信息。

当需要查询lock的信息时,只管拿即可,MyBatis会自动去查询。

​ 例:给上述测试代码加上:System.out.println(key.getLock().getLockName());,之后,MyBatis就会执行两条SQL去查询出锁子的信息。

设置了懒加载之后,也可以在resultMap的association标签中使用,fetchType属性开启立即加载。eager表示立即加载。写上之后,就会覆盖MyBatis的全局设置。

<resultMap id="myKey02" type="nuc.pojo.Key"><id property="id" column="id"/><result property="keyName" column="keyname"/><!-- 可以告诉MyBatis,让他自己去查询(调用一个查询)select="":指定一个查询SQL的唯一标识,MyBatis自动调用这个指定的SQL将查询出的结果封装进对应对象告诉MyBatis将哪一列的值传入进去 column="lid"--><association property="lock" select="nuc.inter.LockDao.getLockByIdSimple" column="lockid" fetchType="eager"/></resultMap>

collection的分步查询和association类似。

推荐:使用连接查询,因为分步查询的效率不如连接查询

8. 动态SQL

动态SQL时MyBatis最强大的功能,极大的简化我们拼装SQL的操作。

有如下四个基本语法:

ifchoose (when, otherwise)trim (where, set)foreach

(1)if标签

if标签的 test=""属性后:编写判断条件 例:<if test="id!=null">注意:这里不能写&&,可以写and,也可以使用转义字符。例:&amp;

<!-- test="id!=null" 取出传入的JavaBean属性的id的值,判断是否为空--><select id="getTeacherByCondition" resultMap="myTeacher">select * from t_teacher where<if test="id!=null">id > #{id} and</if><if test="teacherName!=null">teacherName like #{teacherName}</if></select>

(2) where标签

可以帮我们去掉前面的and

(3)trim标签,截取字符串

prefix="" :前缀,为我们下面的SQL整体添加一个前缀。

prefixOverrides="":取出多余的字符串,例:and

suffixOverrides="" :去掉多余的字符串,例:AMD

suffix="":为所有SQL添加后缀

推荐使用where标签,并将所有的and写在前面。

(4)foreach标签:遍历集合

collection:指定要遍历的集合item:每次遍历出的元素起一个变量名,方便引用。open:开始遍历时的拼接字符串close:结束时拼接的字符串separator:遍历的元素之间的分隔符。index: 索引 如果遍历的是list,index保存就是当前元素的索引如果遍历的是map,index保存的就是当前元素的key。

<select id="getTeacherList" resultType="myTeacher">select * from t_teacher where id IN<where><foreach collection="ids" item="id_item" open=" (" close=")" separator=",">#{id_item}</foreach></where></select>

(5)choose标签

只进一个when标签。

<select id="getTeahcers" resultType="myTeacher">select * from t_teacher<where><choose><when test="id!=null">id = #{id}</when><when test="name!=null">teachername=#{name}</when><when test="birth!=null">birth_date=#{birth}</when><otherwise>1=1</otherwise></choose></where></select>

(6)set标签,update语句使用

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

<update id="updateTeacher">update t_teacher<set><if test="teacherName!=null">teachername = #{teacherName},</if><if test="className!=null">class_name=#{className},</if></set><where>id=#{id}</where></update>

(7)bind标签

绑定一个表达式给一个变量。

(8)sql标签:抽取可重用的SQL语句

搭配include标签使用,引入SQL。

<sql id="sql01">select * from t_teacher</sql><select id="getTeacherById" resultMap="myTeacher"><include refid="sql01"></include></select>

9. 扩展:OGNL

OGNL:( Object Graph Navigation Language )对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等。

有类似以下的类,属性如下:

class Person{lastName;email;Address;city;province;street;}

用点分隔各层属性,就是导航图。如:person.Address.street

OGNL 不仅可以访问属性,也可以访问方法,静态方法,构造方法,运算符,逻辑运算符。

在MyBatis中,除了可以使用传入的参数来判断外,还可以使用两个额外的参数。

_parameter代表传入的参数 单个参数:就代表传入的参数。多个参数:就代表多个参数组成的Map_databaseId代表当前环境:MySQL,Oracle等 配置的数据库移植,才可以使用。databaseIdProvider标签。(在MyBatis核心配置文件中配置)

10. 缓存机制

缓存:暂时的存储一些数据,加快系统的查询速度。MyBatis中的缓存机制,本质就是一个Map,能保存查询出的一些数据。 一级缓存:线程级别的;本地缓存;SqlSession级别的缓存(和数据库的一次会话);二级缓存:全局缓存;其他的sqlSession也能使用。

查询的时候,先看缓存中有没有,没有的话再去数据库中查。

(1)一级缓存

默认存在的。只要之前查询过的数据,MyBatis就会将其保存在缓存中(Map中),下次直接从缓存中拿。

@Testpublic void test01(){SqlSession sqlSession = sqlSessionFactory.openSession();TeacherDao teacherDao = sqlSession.getMapper(TeacherDao.class);Teacher teacher01 = teacherDao.getTeacherById(1);Teacher teacher02 = teacherDao.getTeacherById(1);System.out.println(teacher01==teacher02);}

输出:只执行一次SQL语句

一级缓存失效的情况:

一级缓存是sqlSession级别的缓存,不同的sqlSession,使用不同的一级缓存。 一个sqlSession查询到的数据会保存在这个sqlSession的一级缓存中,第二次如果查询相同的数据,就从缓存中拿,而不是再去查询数据库。

/*** 一级缓存失效的几种情况:* */@Testpublic void test02(){//第一次会话SqlSession sqlSession01 = sqlSessionFactory.openSession();TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);Teacher teacher01 = teacherDao01.getTeacherById(1);//第二次会话SqlSession sqlSession02 =sqlSessionFactory.openSession();TeacherDao teacherDao02 = sqlSession02.getMapper(TeacherDao.class);Teacher teacher02 = teacherDao02.getTeacherById(1);System.out.println(teacher01==teacher02);//false}

输出:执行了两次SQL,查询数据库两次。

在两次拿数据之间,执行任意一个增删改操作。一级缓存就会失效。原因是:增删改操作会将缓存清空。

@Testpublic void test02(){//第一次会话SqlSession sqlSession01 = sqlSessionFactory.openSession();TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);Teacher teacher01 = teacherDao01.getTeacherById(1);//执行一次增删改操作Teacher teacher = new Teacher();teacher.setId(2);teacher.setTeacherName("Test");teacherDao01.updateTeacher(teacher);Teacher teacher02 = teacherDao01.getTeacherById(1);System.out.println(teacher01==teacher02);//mit();}

输出:在一个sqlSession中查询同样的数据,也会执行两次SQL语句。

手动清空缓存:

@Testpublic void test02(){//第一次会话SqlSession sqlSession01 = sqlSessionFactory.openSession();TeacherDao teacherDao01 = sqlSession01.getMapper(TeacherDao.class);Teacher teacher01 = teacherDao01.getTeacherById(1);//手动清空缓存sqlSession01.clearCache();Teacher teacher02 = teacherDao01.getTeacherById(1);System.out.println(teacher01==teacher02);//false}

总结:每次查询,先去缓存中查看有没有该数据,如果没有才发起新的SQL。每个sqlSession拥有自己的缓存。

MyBatis的缓存就是一个Map,如下:

保存的数据的key:hashCode+查询的SqlId+编写的sql查询语句+参数,我们可以打开它的源码看一下。

(2)二级缓存

二级缓存:全局作用域缓存,默认不开启,需要手动配置。(从一级缓存搬到二级缓存)在MyBatis的配置文件中开启缓存。二级缓存在sqlSession关闭或提交之后才会生效。

MyBatis中使用二级缓存的步骤:

在MyBatis配置文件中开启二级缓存。

<!-- 开启二级缓存,并告诉MyBatis哪个Dao查询的时候需要用到二级缓存--><setting name="cacheEnabled" value="true"/>

在Dao的实现文件中使用二级缓存。TeacherDao.xml文件中加上。

<cache/>

POJO需要实现Serializable接口。(序列化接口)

测试代码:

@Testpublic void test_03(){//创建两个会话SqlSession sqlSession01 = sqlSessionFactory.openSession();SqlSession sqlSession02 = sqlSessionFactory.openSession();UserDao UserDao01 = sqlSession01.getMapper(UserDao.class);UserDao UserDao02 = sqlSession02.getMapper(UserDao.class);//第一个Dao查询一号用户User user1 = UserDao01.getUserById(1);sqlSession01.close();System.out.println("user1: "+user1);//第二个Dao也查询一号用户User user2 = UserDao02.getUserById(1);sqlSession02.close();System.out.println("user2: "+user2);System.out.println("user1是否与user2相同:"+(user1==user2));}}

输出:

显然只执行一次SQL

二级缓存总结:

二级缓存是namespace级别的缓存,哪个Dao要用缓存,就配置在哪个Dao的实现文件中。

在Dao中的<cache>标签中可以配置的属性:

eviction:缓存回收策略: LRU – 最近最少使用的:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

默认的是 LRU。 flushInterval:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 size:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出 readOnly:只读,true/false true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

不会出现二级缓存和一级缓存中有同一个数据。

二级缓存:一级缓存关闭了之后,才有二级缓存的数据。一级缓存:当二级缓存中没该数据时,就先看一级缓存,当一级缓存中也没有时,就去查询数据库。 数据库中查询出来的数据,就放在一级缓存中。

任何时候都是先看二级缓存,再看一级缓存,再数据库。

缓存原理:

sql标签(增删改查)的flushCache属性:

增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。查询默认flushCache=false。sqlSession.clearCache():只是用来清除一级缓存

11. 整合第三方缓存

因为MyBatis的缓存太过简陋,就是一个Map。

MyBatis将Cache做成了一个接口,可以使用其他第三方缓存进行代替。

(1)导入jar包

ehcache-core:ehcache的核心包

mybatis-ehcache:ehcache和MyBatis的整合包

依赖的日志包:

slf4j-api:

slf4j-log4j12:

<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.11</version></dependency><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.1.0</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.0-alpha1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>2.0.0-alpha1</version><scope>test</scope></dependency>

(2)ehcache的配置文件

ehcache的配置文件叫ehcache.xml,放在类路径下。使用ehcache之后,pojo类可以不用实现 Serializable 接口。

<?xml version="1.0" encoding="UTF-8"?><ehcache xmlns:xsi="/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"><!-- 磁盘保存路径 --><diskStore path="E:\ehcache" /><defaultCache maxElementsInMemory="100000" maxElementsOnDisk="10000000"eternal="false" overflowToDisk="true" timeToIdleSeconds="120"timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"></defaultCache></ehcache><!-- 属性说明:l diskStore:指定数据在磁盘中的存储位置。l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略以下属性是必须的:l maxElementsInMemory - 在内存中缓存的element的最大数目 l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上以下属性是可选的:l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)-->

(3)在Mapper.xml文件中配置使用自定义的缓存

<!-- 使用的是MyBatis的默认二级缓存,type:指定相应的自定义缓存类--><cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

【系列文章】

1. Git&GitHub(基础)

2. Git&GitHub(进阶)

3. Java多线程

4. JavaScript 总结

5. SpringMVC(一)

6. SpringMVC(二)

……

关注博主🤞🤞

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。