文章目录
MyBatis是什么?
-
MyBatis是在MVC模式中的DAO层里封装了JDBC的一种框架。
-
MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。
-
MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
-
MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
-
MyBatis可以简化JDBC操作,实现数据的持久化。如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-77RZ6lyO-1576760304337)(https://s2.ax1x.com/2019/12/18/QTsfHO.png)]
MyBatis的优点
-
MyBatis是一个半自动化的持久化层框架。
-
JDBC:SQL夹在Java代码块里,耦合度高导致硬编码内伤,维护不易且实际开发需求中SQL是有变化,频繁修改的情况多见。硬编码:
Configuration conf = new Configuration(); conf.setName("myProject");
-
Hibernate和JPA:长难复杂SQL,对于Hibernate而言处理也不容易,内部自动生产的SQL,不容易做特殊优化。基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,导致数据库性能下降。
-
对开发人员而言,核心SQL还是需要自己优化。
-
SQL和Java编码分开,功能边界清晰,一个专注业务、 一个专注数据。
ORM:Object Relational Mapping(关系型数据库)
对象与表一一映射。MyBatis是ORM的一个实现(还有Hibernate)。ORM可以使得开发人员像操作对象一样操作数据库表。
MyBatis架构图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOyRfiSB-1576760304338)(https://s2.ax1x.com/2019/12/18/QTsI4H.png)]
MyBatis的JAR
-
核心包:
mybatis-3.5.3.jar
-
依赖包:
ant-1.10.3.jar ant-launcher-1.10.3.jar asm-7.0.jar cglib-3.2.10.jar commons-logging-1.2.jar javassist-3.24.1-GA.jar log4j-1.2.17.jar log4j-api-2.11.2.jar log4j-core-2.11.2.jar ognl-3.2.10.jar slf4j-api-1.7.26.jar slf4j-log4j12-1.7.26.jar
-
数据库包:比如Oracle(
ojdbc.jar
)、MySQL(mysql-connector-java-5.1.7-bin.jar
)
MyBatis配置文件
全局配置文件:SqlMapConfig.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>
<!--
1、MyBatis可以使用properties来引入外部properties配置文件的内容。
resource:引入类路径下的资源。
url:引入网络路径或者磁盘路径下的资源。
-->
<properties resource="dbconfig.properties"></properties>
<!--
2、settings包含很多重要的设置项。
setting:用来设置每一个设置项。
name:设置项名。
value:设置项取值。
-->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="false"/>
<setting name="lazyLoadingEnabled" value="false"/>
</settings>
<!--
3、typeAliases:别名处理器,可以为我们的Java类型起别名 。
别名不区分大小写
-->
<typeAliases>
<!--(1)typeAlias:为某个Java类型起别名
type:指定要起别名的类型全类名。默认别名就是类名小写,比如employee
alias:指定新的别名
-->
<typeAlias type="com.colin.bean.Employee" alias="emp"/>
<!--(2)package:为某个包下的所有类批量起别名。
name:指定包名(为当前包以及下面所有的子包的每一个类都起一个默认别名(类名小写))
-->
<package name="com.colin.bean"/>
<!--(3)批量起别名的情况下,使用 @Alias 注解为某个类型指定新的别名 -->
</typeAliases>
<!--
4、environments:环境,MyBatis可以配置多种环境。
default:指定使用某种环境,可以达到快速切换环境。
environment:配置一个具体的环境信息,必须有两个标签。id代表当前环境的唯一标识。
(1)transactionManager:事务管理器。
type:事务管理器的类型。JDBC(JdbcTransactionFactory)|MANAGED(ManagedTransactionFactory)。 自定义事务管理器:实现TransactionFactory接口。type指定为全类名。
(2)dataSource:数据源。
type:数据源类型。UNPOOLED(UnpooledDataSourceFactory)|POOLED(PooledDataSourceFactory)|JNDI(JndiDataSourceFactory)。自定义数据源:实现DataSourceFactory接口,type是全类名
-->
<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/mybatis02?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="colin"/>
</dataSource>
</environment>
</environments>
<!--
5、databaseIdProvider:支持多数据库厂商。
type="DB_VENDOR":VendorDatabaseIdProvider
作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),MyBatis就能根据数据库厂商标识来执行不同的SQL。MySQL,Oracle,SQL Server...
-->
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同的数据库厂商起别名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
</databaseIdProvider>
<!-- 将我们写好的SQL映射文件(XXXMapper.xml)一定要注册到全局配置文件中 -->
<!--
6、mappers:将SQL映射注册到全局配置中 -->
<mappers>
<!-- mapper:注册一个SQL映射。
(1)注册配置文件:
resource:引用类路径下的SQL映射文件。
mybatis/mapper/EmployeeMapper.xml
url:引用网路路径或者磁盘路径下的SQL映射文件。
file:///var/mappers/AuthorMapper.xml
(2)注册接口:
class:引用(注册)接口。
1、有SQL映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下。
2、没有SQL映射文件,所有的SQL都是利用注解写在接口上。
推荐:
比较重要的,复杂的Dao接口我们来写SQL映射文件。
不重要,简单的Dao接口为了开发快速可以使用注解。
-->
<!-- <mapper resource="mapper/EmployeeMapper.xml"/> -->
<!-- <mapper class="com.colin.dao.EmployeeMapperAnnotation"/> -->
<!-- 批量注册: -->
<mapper resource="config/sqlmap/User.xml"/>
</mappers>
</configuration>
注意:标签
<configuration></configuration>
内的标签顺序,它的源码提示顺序为:
Content Model : (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?,
objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?,
mappers?)
typeAliases
MyBatis内置的默认别名:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FWIfLK9M-1576760304339)(https://s2.ax1x.com/2019/12/18/QH6LtO.png)]
Setting
这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXSMULqi-1576760304340)(https://s2.ax1x.com/2019/12/18/QH670x.png)]
映射文件:XXXMapper.xml
必须遵循以下约定:
namespace
的值必须和接口的完整路径(全类名)一致。mapper.xml
文件中标签id
值必须和接口中的方法名一致。mapper.xml
文件中标签的parameterType
类型必须和接口中的输入参数类型一致。如果mapper.xml
的标签中没有parameterType
,则说明方法没有输入参数。mapper.xml
文件中标签的resultType
类型必须和接口中的方法返回值类型一致。如果没有resultType
,则说明方法的返回值为void
。
<?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="test">
<select id="findById" parameterType="int" resultType="com.colin.entity.User">
select * from user where id = #{id}
</select>
</mapper>
Log4j配置文件
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
MyBatis操作数据库的两种方式之一:SqlSession
-
项目目录,如图:
-
实体类:User
package com.colin.entity; public class User { private Integer id; private String username; private String password; public User() { } public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + '}'; } }
-
测试类:Test1(使用JUint 4)
package com.colin.test; import com.colin.entity.User; 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; public class Test1 { @Test public void testFindById() throws IOException { //1、读取配置文件。 InputStream input = Resources.getResourceAsStream("SqlMapConfig.xml"); //2、利用SqlSessionFactoryBuilder创建SqlSessionFactor。 SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder(); SqlSessionFactory ssf = ssfb.build(input); //3、使用SqlSessionFactory获取sqlSession对象。一个SqlSession对象代表和数据库的一次会话。 SqlSession session = ssf.openSession(); //4、使用SqlSession根据方法id进行操作 User user = session.selectOne("findById", 1); System.out.println(user); //5、关闭session session.close(); } }
-
运行结果
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. User{id=1, username='郭德纲', password='1973'}
session.selectOne("需要查询的SQL的namespace的id","SQL的参数值");
- 如果使用的事务方式为
<transactionManager type="JDBC"/>
,则增删改都需要手工提交commit
,即session.commit();
- 所有的标签
<select>
、<update>
等,都必须有SQL语句,但是SQL参数值可选。比如:
select * from student where stuno = #{xx};
SQL有参数:session.insert(statement, 参数值);
SQL没参数:session.insert(statement);
优化获取Session
使用一个工具类来创建Session,然后在测试类中直接调用即可。
-
MyBatisUtils工具类
public class MyBatisUtils { static SqlSessionFactory ssf = null; // 静态代码块 static { InputStream input = null; try { input = Resources.getResourceAsStream("config/SqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder(); ssf = ssfb.build(input); } // 目的:获取SqlSession public static SqlSession getSession() { return ssf.openSession(); } }
-
测试模糊查询
@Test public void testFindByUserName() throws IOException { SqlSession session = MyBatisUtils.getSession(); //业务逻辑 //需求:根据用户名模糊查询用户 List<User> userList = session.selectList("findByUserName", "张%"); for (User list : userList) { System.out.println(list); } //关闭session session.close(); }
-
运行结果
log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. User{id=3, username='张鹤伦', password='1985'} User{id=5, username='张云雷2', password='1111'}
参数类型之输入参数:parameterType
- 简单类型(8个基本类型 +
String
)是可以使用任何占位符#{XXX}
。 - 对象类型则必须是对象的属性
#{属性名}
。
#{} 与 ${}的不同之处
-
#{任意值}
${value}
,其中的标识符只能是value
。 -
#{}
:会自动给String
类型加上''
(自动类型转换)
${}
:原样输出,但是适合于动态排序(动态字段)
比如:select stuno, stuname, stuage from student where stuname = #{value};
相当于
select stuno, stuname, stuage from student where stuname = '${value}';
动态排序:select stuno, stuname, stuage from student order by ${value} asc;
-
#{}
:可以防止SQL注入。
${}
:不防止。
${} 与 #{}的相同之处
- 都可以获取对象的值、嵌套类型对象。
-
获取对象值
模糊查询,方式一:
select stuno, stuname, stuage from student where stuage = #{stuAge} or stuname like #{stuName};
Student student = new Student(); student.setStuAge(24); student.setStuName("%w%"); List<Student> students = studentMapper.queryStudentBystuageOrstuName(student) ;//接口的方法->SQL
模糊查询,方式二:
select stuno, stuname, stuage from student where stuage = #{stuAge} or stuname like '%${stuName}%';
Student student = new Student(); student.setStuName("w");
-
嵌套类型对象
XML:
<select id="queryStudentByAddress" resultType="student" parameterType="student"> select * from student where homeaddress = #{address.homeAddress} or schooladdress = '${address.schoolAddress}' </select>
test:
Student student = new Student(); Address address = new Address(); address.setHomeAddress("xa"); address.setSchoolAddress("bj"); student.setAddress(address); List<Student> students = (List<Student>)mapper.queryStudentByAddress(student);
- 都可以获取对象类型。
#{属性名}
${属性名}
比如输入对象为HashMap
,则泛型约束必须为Map<String, Object>
。例如... where stuage= #{stuAge};
则用Map
中key
的值匹配占位符#{stuAge}
,如果匹配成功 就用Map
的value
替换占位符。
参数类型之输出参数:resultType和resultMap
二者不能同时存在
resultType:直接表示返回类型。
resultType
返回值类型是一个对象(如Student
)的话,则无论返回一个、还是多个,在resultType
都写成com.colin.entity.User
即resultType="com.colin.entity.User"
。
- 简单类型(8个基本 +
String
)。 - 输出参数为实体对象类型。
- 输出参数为实体对象类型的集合:虽然输出类型为集合,但是
resultType
依然写集合的元素类型(resyltType="Student"
)。 - 输出参数类型为
HashMap
:HashMap
本身是一个集合,可以存放多个元素,但是根据提示发现当返回值为HashMap
时,查询的结果只能是1个学生,由此得出结论:一个HashMap
对应一个学生的多个元素(多个属性)即一个Map
,一个学生。 - 若想打印多个
HashMap
,在外嵌套一个集合List<HashMap<String,Object>>
。集合式的HashMap
如同二维数组:
{
{1,zs,23,xa}, //一个HashMap对象
{2,ls,24,bj},
{3,ww,25,tj}
}
resultMap:对外部resultMap的引用。
resultType
和resultMap
功能类似,都是返回对象信息,但是resultMap
要更强大一些 ,可自定义。因为resultMap
要配置一下表和类的一一对应关系,所以说就算你的字段名和你的实体类的属性名不一样也没关系,都会给你映射出来。但是,resultType
就比较鸡肋了,必须字段名一样,比如说cId
和c_id
这种的都不能映射。
resultMap
描述如何将结果集映射到Java对象。resultMap
属性:id
type
resultMap
子元素:id
result
association
collection
如图:
当属性名和字段名不一致时,除了使用resultMap
以外,还可以使用resultType + HashMap
:
-
resultMap
<resultMap type="student" id="queryStudentByIdMap"> <!-- 指定类中的属性和表中的字段对应关系 --> <id property="stuNo" column="id"/> <result property="stuName" column="name"/> </resultMap>
-
resultType + HashMap
select 表的字段名 "类的属性名" ... from 表 来指定字段名和属性名的对应关系
<select id="queryStudentByIdWithHashMap" parameterType="int" resultType="student" > select id "stuNo",name "stuName" from student where id = #{id} </select>
注意:如果10个字段,但发现某一个字段的结果始终为默认值(0, 0.0, null),则可能是 表的字段和类的属性名字写错。
关联查询
一对一
- 业务扩展类
创建实体类时,继承一个属性多的类,重写一个属性少的类。
public class XxxBusiness extends 属性多entity{
属性少的类的属性
}
注意:返回值为XxxBusiness
类。
核心:用resultType
指定类的属性包含多表查询的所有字段。
resultMap
:
对于一对一表连接的处理方式通常为在主表的POJO中添加嵌套另一个表的POJO,然后在mapper.xml
中采用association
节点元素进行对另一个表的连接处理。例如:
<!-- 订单查询关联用户的resultMap
将整个查询的结果映射到com.colin.Orders中
-->
<resultMap type="com.colin.Orders" id="OrdersUserResultMap">
<!-- 配置映射的订单信息 -->
<!-- id:指定查询列中的唯一标识,订单信息中的唯一标识,如果有多个列组成唯一标识,则配置多个id。
column:订单信息的唯一标识,即列。
property:订单信息的唯一标识,即列所映射到Orders中哪个属性。
-->
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 配置映射的关联的用户信息 -->
<!-- association:用于映射关联查询单个对象的信息。
property:要将关联查询的用户信息映射到Orders中哪个属性。
-->
<association property="user" javaType="com.colin.User">
<!-- id:关联查询用户的唯一标识。
column:指定唯一标识用户信息的列。
javaType:映射到User的哪个属性。
-->
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
</association>
</resultMap>
一对多
MyBatis:多对一,多对多的本质就是一对多的变化。
resultMap
的处理方式为在订单表数据的POJO中添加一个List
,List
中为订单明细表的属性,在mapper.xml
中采用采用collection
节点元素,比如:
表:student studentclass
(关联:classid
)
类:student studentClass
(关联:List<Student> students
)
select c.*,s.* from student s
inner join studentclass c
on c.classid = s.classid
where c.classid = 1;
<resultMap type="studentClass" id="class_student_map">
<id property="classId" column="classid"/>
<result property="className" column="classname"/>
<!--
collection:对关联查询到多条记录映射到集合对象中。
property:将关联查询到多条记录映射到com.colin.entity.Student哪个属性。
ofType:指定映射到List集合属性中POJO的类型。
-->
<collection property="students" ofType="Student">
<id property="stuNo" column="stuno"/>
<result property="stuName" column="stuname"/>
<result property="stuAge" column="stuage"/>
</collection>
</resultMap>
类型处理器(类型转换器)
MyBatis类型转换器适用于Java实体类中的类型和数据库中的类型不对应时。
MyBatis自带一些常见的类型处理器,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ImwlsYoR-1576760304342)(https://s2.ax1x.com/2019/12/19/QbQRdP.png)]
自定义MyBatis类型处理器
每当MyBatis设置参数到PrepareStatemen
t或者从ResultSet
结果集中取值时,就会用到TypeHandler
来处理数据库类型与Java类型之间的转换。
比如:Java实体类中有一个Boolean类型的字段flag,对应到数据库flag字段中类型是int。这时我们会使用TypeHandler来进行入库和查询的数据转换。
实现自定义类型转换器的步骤:
-
创建转换器:需要实现
TypeHandler
接口。
通过阅读源码发现,此接口有一个实现类BaseTypeHandler
,因此要实现转换器有两种选择:- 实现
TypeHandler
接口。 - 继承
BaseTypeHandler
。
- 实现
-
进行自定义类型转换器的注册(在面向接口编程中好多我们实现或者集成人家的接口或者是实现类都需要去注册,这样子程序在运行时才会知道运行的实现类是那个)。注册类型转换器的方式有两种:
- 进行全局的注册。在核心配置文件中添加:
<typeHandlers> <typeHandler handler="com.colin.util.MyTypeHandler" javaType="Boolean" jdbcType="NUMBER"/> </typeHandlers>
这种配置的作用域是全局的,也就是说所有的我们书写的
Mapper
中凡是满足Java类型是Boolean
数据库类型是NUMBER
或者是int
时(满足这个条件javaType=“Boolean” jdbcType=“NUMERIC”
),都会执行这个MyTypeHandler
。- 局部注册。在对应的
Mapper
文件中添加resultMap
标签:
<resultMap type="dept" id="deptMap"> <result column="flage" property="flage" typeHandler="com.colin.util.MyTypeHandler"/> </resultMap>
需要注意的问题:
INTEGER
类型。
注意#{stuNo}
中存放的是属性值,需要严格区分大小写。
insert into student(stuno,stuname,stuage,graname,stusex) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName}, #{stuSex, javaType=boolean, jdbcType=INTEGER});
MyBatis操作数据库的两种方式之二:接口式编程。
即动态代理方式的Mapper。
原则:约定优于配置。
约定的目标:省略掉statement
,即根据约定可以直接可以定位出SQL语句。
约定(匹配)的过程:
- 根据接口名能找到
mapper.xml
文件(根据的是namespace
= 接口的完整路径) - 根据接口的方法名找到
mapper.xml
文件中的SQL标签(方法名 = SQL标签id
值)
以上2点可以保证:当我们调用接口中的方法时,程序能自动定位到某一个mapper.xml
文件中的SQL标签。
习惯:将SQL映射文件(mapper.xml)和其接口放在同一个包中。(注意修改核心配置文件中加载
mapper.xml
文件的路径)
通过反射机制获取接口
session.getMapper(接口.class);
-
测试类
@Test public void testFindById() { //1. 通过优化后的工具类来获取Session SqlSession session = MyBatisUtils.getSession(); //2. 获取动态代理对象 UserMapper mapper = session.getMapper(UserMapper.class); //3. 代码测试 User user = mapper.findById(1); System.out.println(user); //4、关闭session session.close(); }
-
接口类
public interface UserMapper { User findById(Integer id); ... }
-
映射文件mapper.xml
<mapper namespace="com.colin.dao.UserMapper"> <select id="findById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> ... </mapper>
通过session
对象获取接口,再调用该接口中的方法,程序会自动执行该方法对应的SQL。
标签
where标签
select stuno, stuname, stuage from student <where> and stuname = #{stuName} and stuage = #{stuAge}
<where>
会自动处理第一个<if>
标签中的and
,但不会处理之后<if>
中的and
。
foreach标签
比如查询学号为1、2、53的学生信息。
select stuno, stuname from student where stuno in(1,2,53);
<foreach>
可以迭代的类型有:数组、对象数组、集合、属性。
-
简单类型的数组
无论编写任何代码时,传递的是什么参数名(stuNos
),在mapper.xml
中 必须用array
代替该数组。 -
对象数组
比如:Student[] students = {student0, student1, student2}
每个student
包含一个学号属性。parameterType="Object[]"
<foreach collection="array" open=" and stuno in (" close=")" item="student" separator=","> #{student.stuNo} </foreach>
-
集合
无论编写代码时,传递的是什么参数名(stuNos
),在mapper.xml
中 必须用list
代替该数组。 -
属性(比如Grade类
List<Integer> ids
)select * from student open: select * from student and stuno in ( item: select * from student and stuno in (1253 close: select * from student and stuno in (1,2,53)
SQL片段
在开发中,SQL的拼接很常见,有很多拼接的SQL具有重复性高的特点,这时最好把重复的SQL抽取出来,作为公用的SQL片段。比如Java中使用方法,数据库中使用存储过程、存储函数,而MyBatis里则使用的是SQL片段。
- 提取相似代码。
<where>...</where>
- 引用。
- 在XML文件的开头 :
<sql>相似代码</sql>
- 在原来的位置:
<include refid="..."></include>
- 如果引入外部XML文件的代码段,则
<include refid="..."></include>
标签的refi
d为:namespace.id
- 在XML文件的开头 :
调用存储过程
<select id="queryCountByGradeWithProcedure" statementType="CALLABLE" parameterType="HashMap">
{
CALL queryCountByGradeWithProcedure(
#{gName, jdbcType=VARCHAR, mode=IN},
#{scount, jdbcType=INTEGER, mode=OUT}
)
}
</select>
其中通过statementType="CALLABLE"
设置SQL的执行方式是存储过程。 存储过程的输入参数gName
需要通过HashMap来指定。
在使用时,通过HashMap的Put
方法传入输入参数的值。通过HashMap的Get
方法 获取输出参数的值。
存储过程无论输入参数是什么值,语法上都需要用Map来传递该值。
注意:
JAR问题:ojdbc6.jar
不能在调存储过程时使用回车、tab
,但是ojdbc7.jar
可以。
如果报错:No enum constant org.apache.ibatis.type.JdbcType.xx
则说明MyBatis
不支持 xx 类型,需要查表。
缓存
一级缓存:同一个SqlSession对象。
MyBatis默认开启一级缓存,如果用同样的SqlSession
对象查询相同的数据,则只会在第一次 查询时向数据库发送SQL语句,并将查询的结果放入到SqlSession
中(作为缓存),后续再次查询该同样的对象时,则直接从缓存中查询该对象即可(即省略了数据库的访问),如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLUjvWCd-1576760304344)(https://s2.ax1x.com/2019/12/19/Qqawlt.png)]
二级缓存
MyBatis自带二级缓存:**同一个namespace
**生成的mapper
对象,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNzYLKj7-1576760304344)(https://s2.ax1x.com/2019/12/19/QqBRET.png)]
MyBatis默认情况没有开启二级缓存,需要手工打开:
-
全局配置文件。
<!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/>
-
在具体的
mapper.xml
中声明开启。<mapper namespace="com.colin.mapper.StudentMapper"> <!-- 声明此namespace开启二级缓存 --> <cache/>
根据异常提示:
NotSerializableException
可知,MyBatis的二级缓存是将对象 放入硬盘文件中。
序列化:内存 -> 硬盘
反序列化:硬盘 -> 内存
准备缓存的对象,必须实现了序列化接口(如果开启的缓存为Namespace="com.colin.mapper.StudentMapper"
),可知序列化对象为Student
,因此需要将Student
序列化(序列化Student
类,以及Student
的级联属性、和父类)。
触发将对象写入二级缓存的时机:SqlSession
对象的close()
方法。
结论:只要产生的XXXMapper
对象来自于同一个namespace
,则这些对象共享二级缓存。
注意:二级缓存的范围是同一个namespace
,如果有多个XXXMapper.xml
的namespace
值相同,则通过这些xxxMapper.xml
产生的xxMapper
对象仍然共享二级缓存。
三方提供的二级缓存:
ehcache、memcache
要想整合三方提供的二级缓存 (或者自定义二级缓存),必须实现org.apache.ibatis.cache.Cache接口,该接口的默认实现类是PerpetualCache
整合ehcache二级缓存:
a.
ehcache-core.jar
mybatis-Ehcache.jar
slf4j-api.jar
b.编写ehcache配置文件 Ehcache.xml
c.开启EhCache二级缓存
在xxxMapper.xml中开启
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!-- 通过property覆盖Ehcache.xml中的值 -->
<property name="maxElementsInMemory" value="2000"/>
<property name="maxElementsOnDisk" value="3000"/>
</cache>
禁用与清理缓存
禁用缓存:
在statement
中设置useCache=false
可以禁用当前select
语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true
,即该SQL使用二级缓存。
清理缓存:
- 与清理一级缓存的方法相同:
commit();
一般执行增删改时会清理掉缓存,设计的原因是为了防止脏数据。
commit()
会清理一级和二级缓存。但是在清理二级缓存时,不能是查询自身的commit()
。 - 在
mapper
的同一个namespace
中,如果有其它insert
、update
、delete
操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读。设置statement
配置中的flushCache="true"
属性,默认情况下为true
即刷新缓存,如果改成false
则不会刷新。使用缓存时如果手动
修改数据库表中的查询数据会出现脏读。
缓存命中率
参考文章:
关于Mybatis缓存命中率的一己之见
逆向工程
表、类、接口、mapper.xml
四者密切相关,因此当知道一个的时候其他三个应该可以自动生成。即表 -> 其他三个。
实现配置:
- JAR包:
mybatis-generator-core.jar
、mybatis.jar
、ojdbc.jar
。 - 配置逆向工程的配置文件
generator.xml
。 - 执行Java的
main
方法。
从GitHub上Clone
逆向工程项目,然后导入该项目。修改逆向工程的配置文件generator.xml
即可。
Log4j
设置步骤
- JAR包:
log4j.jar
(MyBatis.zip
中lib
中包含此) - 开启日志,设置全局配置文件:
<settings>
<!-- 开启日志,并指定使用的具体日志 -->
<setting name="logImpl" value="LOG4J"/>
</settings>
如果不指定,MyBatis就会根据以下顺序寻找日志:
SLF4J -> Apache Commons Logging -> Log4j 2 -> Log4j -> JDK logging
3. 编写配置日志输出文件
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
日志级别
DEBUG < INFO < WARN < ERROR
如果设置为INFO
,则只显示INFO
及以上级别的信息。
建议:在开发时设置DEBUG,在运行时设置为INFO
或以上。
可以通过日志信息,详细的阅读MyBatis执行情况(观察MyBatis实际执行SQL语句 以及SQL中的参数和返回结果)
延迟加载(懒加载)
如果不采用延迟加载(即立即加载),查询时会将“一”和“多”都查询,即班级、班级中的所有学生。如果想要 暂时只查询“一”的一方,而“多”的一方先不查询,而是在需要的时候再去查询 则需要使用延迟加载。
一对一:学生、学生证。
MyBatis中使用延迟加载,需要先配置全局配置文件:
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭立即加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
如果增加了mapper.xml
,要修改全局配置文件(将新增的mapper.xml
加载进去)。
通过DEBUG可以发现,如果程序只需要学生,则只向数据库发送了查询学生的SQL,当我们后续需要用到学生证的时候,再第二次发送查询学生证的SQL。
一对多:班级 -> 学生,和一对一的延迟加载配置方法相同。
延迟加载的步骤:先查班级,按需查询学生
- 配置全局配置文件中的
settings
。 - 配置
mapper.xml
,写2个mapper.xml
:
- 班级
mapper.xml
<select id="queryClassAndStudents" resultMap="class_student_lazyLoad_map">
select * from studentclass
</select>
<resultMap type="studentClass" id="class_student_lazyLoad_map">
<!-- 因为type的主类是班级,因此先配置班级的信息-->
<id property="classId" column="classId"/>
<result property="className" column="className"/>
<!-- 配置成员属性学生,一对多。属性类型property:javaType,属性的元素类型ofType-->
<!-- 然后再查班级对应的学生 -->
<collection property="students" ofType="student" select="org.lanqiao.mapper.StudentMapper.queryStudentsByClassId" column="classid">
</collection>
</resultMap>
即查询学生的SQL是通过select
属性指定,并且通过column
指定外键。
- 学生
mapper.xml
<!-- 一对多,延迟加载需要查询班级中的所有学生 -->
<select id="queryStudentsByClassId" parameterType="int" resultType="student">
select * from student where classId = #{classId}
</select>