Mybatis框架
JDBC编程及存在的问题
JDBC编程步骤
- 首先进行数据准备:在数据库中创建一张表
- 其次是引入jar包:
(jar通过maven管理)
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
(根据自己的需要选择合适的版本)
JDBC存在的问题
- 问题1:在使用JDBC操作数据库前进行连接、操作完之后关闭连接、对并发性能有大的影响
解决方法: 使用链接池,为了达到复用 - SQL语句硬编码在Java代码中,需求改变需要改变Java代码本身
解决方法: 讲SQL和Java代码解耦合,将SQL语句放在配置文件中(XML),需求改变只需要修改配置文件 - 返回的结果存在硬编码问题
解决方法: 将数据库中的数据集映射到Java对象中
Mybatis的介绍
Mybatis的前身是batis。属于Apache旗下开源的数据持久层框架。它支持自定义SQL,存储过程yi’ji高级映射;免除了大部分的JDBC代码以及设置参数和获取结果集的工作;MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis原理
-
MybatisConfig.xml(全局配置文件):配置数据源,事务、引入映射文件
-
Mapper.xml(映射文件):查询SQL,参数类型、结果集的封装
-
SqlSessionFacyory(会话工厂):获取SQLSession
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
- SQLSession:会话操作CRUD
- Executor(执行器):用于缓存实现
Mybatis使用
引入依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--mybatis配置-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
配置全局文件(mybatis.config.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/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
POJO类
存放数据库中某个表对应的映射Java对象类
配置Mapper.xml文件
<?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命令空间,随便写,一般保证命名空间唯一 -->
<mapper namespace="com.tulun.MybatisDemo.StudentMapper">
<!--查询标签:select-->
<select id="selectStudentByID" resultType="com.tulun.MybatisDemo.Student">
select * from Student where SID = #{sid}
</select>
</mapper>
建Mapper接口文件
public interface StudentMapper {
/**
* 通过SID查询数据实体
* @param sid
* @return
*/
public Student selectStudentByID(int sid);
}
将Mapper接口文件的路径配置到全局文件中
<!--配置映射-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
建立一个类执行查询操作
步骤:
- 配置mybatis-config.xml文件(数据源、mapper)
- 创建SqlSessionFactory
- 通过SqlSessionFactory创建SQLSession对象
- 通过SqlSession操作数据库CRUD
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 MybatisTLDemo {
public static void main(String[] args) throws IOException {
//mybatis配置文件
String resource = "mybatis-config.xml";
//通过mybatis提供的Resources类来得到配置文件流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传输mybatis配置文件信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过反射机制来获取对应mapper实例
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//调用mapper实例下方法
Student student = mapper.selectStudentByID(1);
System.out.println(student);
}
}
总结
Mybatis使用方法可分为两种:
- XML配置形式用法
在上述的mybatis-config.xml文件配置完成之后,可以在相应的路径下面建类,类里面有相应的属性以及get(),set()方法。
然后再建立相应的接口文件,在接口文件里建相应的方法,比如:select ,delete,update,insert方法。
之后再配置相应的xml文件,以select为例:
还要在mybatis-config.xml文件中配置映射,比如:
<mapper resource="mapper/StudentMapper.xml"/>
resultType和resultMap的区别:
使用resultType是需要数据库字段(column)和Java类的类型名(property)保持一致,如果不一致则相应属性无法完成映射
使用resultMap进行显性映射,可以解决数据库字段和Java类属性名不一致的问题
注意:
在编写Mapper的文件是需要遵循的开发规范,mybatis才能自动的生成接口的代理对象
1、在mapper.xml文件中namespace和Mapper的接口文件的接口名保持一致
2、Mapper.java接口中的方法名和Mapper.xml中的Statement的ID保持一致
3、Mapper.java接口中方法的输入参数和mapper.xml文件中Statement的parameterType指定的类型一致
4、mapper.java接口中方法的返回值类型和mapper.xml文件中Statement的resultType指定的类型一致
- 注解形式用法
注解的形式SQL语句直接卸载接口上
优点:比较简单
缺点:当SQL发生改变,需要重新编译代码
注解形式与XML文件形式不同的是注解形式不需要配置相应的xml文件,SQL语句直接写在接口文件中
/**
* 通过SID查询学生信息
*/
// public Student getStudentBySID(int SID);
@Results(id = "resultStudent", value= {
@Result(column = "SID", property = "SID", id = true),
@Result(column = "name", property = "name"),
@Result(column = "Ssex", property = "Ssex"),
}
)
/**
* 通过SID查询学生信息
*/
// @Select("select * from Student where SID=#{sid}")
// public Student getStudentBySID(int sid);
@Select("select * from Student where SID=${SID}")
public Student getStudentBySID(Student student);
这次在mybatis-config.xml文件中的映射方式与上一个方法有些不同,这次的resourcre 配置的是接口文件所在的包路径:
<package name="com.tulun.dao"/>
日志
导入jar包
<!--log4j日志文件-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
日志配置文件
创建log4j.properties文件
## debug 级别
log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{yyyy-MM-dd-HH\:mm\:ss,SSS} [%t] [%c] [%p] - %m%n
log4j.logger.com.mybatis=DEBUG /
##输出sql 语句
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
Mybatis配置(mybatis-config.xml)
属性配置(properties)
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
properties 读取外部资源
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。
<dataSource type="POOLED">
// driver 和 url 属性将会由 config.properties 文件中对应的值来替换
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
//此处的username有property中的username替换
<property name="uspasswordername" value="${username}"/>
//此处的password有property中的password替换
<property name="password" value="${password}"/>
</dataSource>
如果属性不在同一个地方配置,那么mybatis的加载顺序为:
(1)读取在properties元素中指定的属性
(2)再根据properties元素中的resource/url属性指定的文件内容并覆盖之前读取的同名属性
(3)最后读取作为方法参数传递的属性,并覆盖之前读取的同名属性
优先级为: 方法参数传递的属性>resource/url加载的属性>properties元素体内的属性
- 有了properties属性之后在查询时可以不用SqlSessionFactory,如:
Properties properties=new Properties();
properties.setProperty("username","root");
//创建会话工厂,传输mybatis配置文件信息
new SqlSessionFactoryBuilder().build(inputStream,properties);
//此时上面的语句就可以代替下面的语句
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
设置(settings)
settings参数比较多,这里只介绍常用的三个(想了解更多可以去Mybatis官网查看:https://mybatis.org/mybatis-3/zh/configuration.html#.)
- catchEnabled是在mybatis中的二级缓存开关
- lazyLoadingEnabled和aggressiveLazyLoading是懒加载的开关配置
<!--全局参数配置-->
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnable" value="true"/>
<setting name="lazyLoadingEnable" value="true"/>
</settings>
类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!--类型别名-->
<typeAliases>
<!--单个别名定义 type:pojo类的路径(全限定名) alias:别名的名称-->
<typeAlias type="com.tulun.MybatisDemo.Student" alias="student"/>
<!--批量的别名定义 name:指定的包名,将包下边的所有pojo类取别名,别名为类名(首字母大小写都行)-->
<package name="com.tulun.pojo"/>
</typeAliases>
在mapper.xml 文件中,定义了很多的Statement(也就是select语句)Statement需要parameterType指定输入参数类型,需要resultType指定输出参数类型,如果指定类型为全路径,开发不方便,可以针对parameterType和resultType指定的类型取别名,别名在配置文件(mybatis-config.xml)中配置typeAliases并在mapper.xml文件中使用
类型处理器(typeHandlers)
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。 具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可选地)将它映射到一个 JDBC 类型。如:
/ ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}
<!-- mybatis-config.xml -->
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
插件(plugins)
环境配置(environment)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
<!--数据源配置-->
<environments default="test">
<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/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
<environment id="online">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="${username}"/>
<property name="password" value="${passwd}"/>
</dataSource>
</environment>
</environments>
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
数据库厂商标识(databaseIdProvider)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider:
<databaseIdProvider type="DB_VENDOR" />
映射(mappers)
mybatis上述行为元素配置完之后我们就要定义SQL映射语句,我们要告诉mybatis语句SQL语句在哪里,在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。
- 类路径资源引用
<!--单个mapper映射,xml和接口可以不再一个目录下
在StudentMapper.xml中定义namespace为mapper接口的地址,映射文件通过
namespace来找到mapper接口文件-->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
- 完全限定资源定位符(包括 file:/// 形式的 URL)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
- 类名
<!--单个mapper映射,指定mapper接口的地址遵循规则
将mapper.xml和mapper.java放在同一个目录下,且文件名相同
-->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
- 包名
<!--批量的mapper映射遵循的规则:将mapper.xml和mapper.java 放在
同一个目录下,且文件名相同-->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
XML映射器
select
在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
**#{id}**告诉mybatis要创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中
// 近似的 JDBC 代码,非 MyBatis 代码...
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
insert
insert标签是用来添加数据的
insert常用的属性:
id属性(必须),唯一标识
parameterType(可以忽略)传入参数类型(入参还有parameterMap)
useGeneratedKeys(可以忽略)开启主键回写
update
update标签是用来修改数据
update的常用的属性:
id属性(必须) 唯一标识
resultType和parameterType(可以忽略)
delete
delete标签是用来删除数据
常用的属性:
id属性(必须) 唯一标识
resultType和parameterType(可以忽略)
Junit测试介绍
Junit是用于编写和运行可重复的自动化测试的开源框架
Junit使用步骤
- 引入Junit依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
- 生成测试类,对想要进行测试的方法进行选择
- 生成的测试类里会有响应的注解
@Test
@Test注解的会被当做测试用例,Junit每一次会创建一个新的测试实例,调用@Test注解方法
@Before
当在测试方法之前需要执行一些操作可以放在@Before注解中读取配置信息
@After
After 注解是在Test注解调用之后才会进行执行,一般是进行资源的回收
多个参数的传递
比如:我们通过学生年龄和性别来查询学生信息
我们使用注解形式来进行查询
但此时会报错:
解决办法:给参数配置@Param注解
#{}和${}区别
#{}使用
@Select("select * from Student where SID=#{sid}")
public Student getStudentBySID(int sid);
运行结果:
由此可见:#{}使用类似于JDBC编程中prepareStatment
${}使用
根据错误提示,我们直到参数的获取使用的是ognl表达式,使用的参数必须提供getter方法。
@Select("select * from Student where SID=${SID}")
public Student getStudentBySID(Student student);
运行结果:
由此可见:${}使用相当于JDBC中的Statement操作
动态SQL
if标签
if表达式一般是放在where条件之后使用,判断参数是否传递使用if test属性(必填)为true或者false,test使用ognl表达式处理,返回true则进入到if标签的SQL,返回false则不会进入if标签
单个参数:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG WHERE 1=1
<if test="title != null">
AND title like #{title}
</if>
</select>
多个参数:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE 1=1
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
where标签
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
trim(where\set)标签
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<trim prefix="WHERE" prefixOverrides="AND ">
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</trim>
</select>
foreach标签
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:
<!-- collection属性(必填) 指定输入参数类型
list:列表
array:数组
map:集合 此时index是键 item是值
item属性:取名称,给集合中单个元素的名称
open属性:开始的字符串
close属性:结束的字符串
separator属性:属性之间的分隔符
-->
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
模糊匹配
比如我们要查询Student表,表中含有“L”的所有用户
SQL: select * from Student Sname like “%L%”;
- 直接在参数上拼接通配符
mapper.xml 文件
select * from Student where Sname like #{name}
将通配符直接拼接在方法参数上
mapper.getStudentByName("%L%");
- Mysql中的concat(,)
Mapper.xml 文件
select * from Student where Sname like concat('%',concat(#{name},'%'))
测试用例:
mapper.getStudentByName("L");
- bind表达式处理
<!-- name属性:取名称
value属性:传递参数
底层借助于ognl表达式处理,参数需要getter方法 -->
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
动态代理
代理模式是Java的一种设计模式,代理模式中代理类和委托类具有相同的接口。
代理类的主要职责就是为委托类预处理消息,过滤消息等功能,代理类的对象本身并不是真正的实现服务,而是通过委托类的对象的相关方法来提供特定的一些服务。
代理类和委托类之间存在关联关系的,一个代理类的对象和一个委托类的对象关联。
访问实际对象,是通过代理对象类方法的。
代理模式分为静态代理、动态代理
**静态代理:**是在程序编译阶段就确定了代理对象
**动态代理:**是在程序运行阶段确定代理对象,它是在运行时根据Java代码指示动态生成的,相比较静态代理,优势在于方便对代理类的函数进行统一处理,而不用修改每个代理类的方法。
Java中提供给的动态代理方式有两种:JDK自带的动态代理和CGLib实现代理
JDK自带的代理方式
JDK自带的代理方式是需要实现invocationHandler接口,实现invoke的方法
- 接口实现
/**
* 接口类
* 定义委托类和代理类共同的方法
*/
public interface IUser {
void talk();
}
- 委托类
/**
* 委托类
* 实现了接口Iuser中的talk方法
*/
public class User implements IUser {
@Override
public void talk() {
System.out.println("doing User.talk");
}
}
- 实现了InvocationHandler接口的代理辅助类
public class UserProxy implements InvocationHandler {
private Object object;
public UserProxy(Object o) {
this.object = o;
}
/**
* 实现动态代理,就需要实现InvocationHandler接口中的invoke方法,该方法有三个参数
* @param proxy :就是动态代理生成的代理对象
* @param method:就是调用的方法
* @param args:表示该方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理类提供委托类提供的功能,也可以提供自己特有的功能
System.out.println("donging UserProxy.invoke");
//调用委托类提供的方法
method.invoke(object,args);
System.out.println("donging UserProxy.invoke end");
return null;
}
}
- main方法
public static void main(String[] args) {
//产生代理对象
IUser iUser = (IUser) Proxy.newProxyInstance(UserProxy.class.getClassLoader(), new Class[]{IUser.class}, new UserProxy(new User()));
iUser.talk();
}
结果:
JDK自带代理方式原理探究
执行之后我们会有疑问,那就是JVM是如何自动实现invoke()方法的调用呢?
接下来,我们在代理辅助类main方法中添加监控组件
//看看JDK生成的自动代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
执行后,JVM生成了一个$Proxy().class文件,查看源码后会发现
通过分析:当代理类实例中调用talk()时,根据Java中的多态,代用了应该是$Proxy()的talk(), $Proxy()重写talk方法
CGLib动态代理
代理提供了一种可扩展机制来控制被代理对象的访问,就是对象的访问做了一层封装,当代理没有接口的类,此时Proxy和InvocationHandler机制不能在使用了(JDK自带的代理模式使用必须需要接口),此时可以使用CGLib库,采用底层字节码技术作为一个类的创建子类,并在子类中采用方法拦截的父类所有方法调用,采用横切逻辑。Spring AOP(横向切面)的技术就是使用动态代理。
- 引入第三方库:
<!--CGLib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>x.x.x</version>
</dependency>
- 父类:
public class CGLibSuper {
public void doing() {
System.out.println("CGLibSuper.doing");
}
}
- 辅助类的实现:
/**
* 通过已有的类产生代理类时
* 在当前辅助类需要实现MethodInterceptor接口
*/
public class CGLibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public <T> T getProxy(Class<T> clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码动态的创建子类实例
return (T)enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLibProxy doing");
//拦截到父类响应的方法
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("CGLibProxy doing end");
return o1;
}
}
- main方法:
public static void main(String[] args) {
CGLibProxy cgLibProxy = new CGLibProxy();
//动态产生的CGLibSuper的子类实例
CGLibSuper proxy = cgLibProxy.getProxy(CGLibSuper.class);
proxy.doing();
}
- 运行结果:
通过执行结果可知,当前产生的代理类调用相应的方法(doing),会被代理辅助类中的intercept方法拦截,在该方法中可以实现扩展,也能调用到父类中的相应方法。
Mybatis的代理模式详解
mybatis中产生的代理方式是JDK自带的方式:其仅在其中实现了接口
- addMappper()
通过代码形式产生的会话工厂实例和读取配置形式是类似的,
通过代码形式中可以看到,Mapper接口是通过Configuration.addMapper()形式来添加
Configuration configuration = new Configuration();
configuration.addMapper(StudentMapper.class);//添加mapper
//addMapper()方法的实现
public <T> void addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
mapper被添加到了MapperRegistry中
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//只添加了接口
if (this.hasMapper(type)) {//不允许接口重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
//接口被放入了HashMap中
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
上述代码中的knownMappers是MapperRegistry中的属性,是一个HashMap对象,key为当前Class对象,Value为MapperProxyFactory实例
- getMapper()
DefaultSqlSession类中的getMapper();
在这里插入代码片
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
Configurantion类中的getMappper();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry中的getMapper()方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
调用SQLSession.getMapper操作,最终会到上面的这个方法,他会根据接口在HashMap中找到对应的Value值,是一个MapperProxyFactory的对象,然后通过调用该对象newInstance的方法,获取到代理对象。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
MapperProxy类:
总结:
Mapper接口在初始sqlSessionFactory的时候进行注册,接口注册到了MapperRegistry的HashMap中,key是Mapper的class,value是当前的Mapper工厂。
addMapper()和getMapper()方法调用的是MapperRegistry中的方法, getMapper实现了 MapperProxyFactory类中的newInstance方法,newInstance方法中调用了MapperProxy类,MapperProxy类使用了InvocationHandler接口与接口中的invoke()方法。
所以sqlSession。getMapper使用了JDK自带的动态代理,产生目标接口Mapper的代理对象,动态代理辅助类MapperProxy。
Mybatis提供的缓存机制
缓存介绍
缓存提供了一种存储方式,将数据存储,访问数据是可以直接访问缓存,减轻数据库的压力,提高数据库性能。
服务器和数据库进行数据交互,进行的操作有增、删、改、查,其中查询操作比较多,查询操作在数据库获取结果集并不会引起数据本身的改变,但是增、删、改都会改变数据库中的数据。
缓存就可以减少服务端与数据库的交互。减轻数据压力,从而提高数据库的性能,缓存主要是用于查询操作。
注意:
在使用缓存时,我们应该避免脏数据问题
**脏数据:**脏数据也就是在数据进行增删改操作是数据库的数据会发生改变,但是缓存数据并没有改变,那么我们查到的就是还没有更新的旧数据,此时我们查到的数据也叫脏数据。
那么缓存数据什么时候更新呢?
当我们进行查询操作时,我们先访问缓存数据,当缓存数据中没有对应的数据时,我们就会查询数据库的数据,然后插入到缓存中,那么接下来的查询就可以直接访问缓存
当我们进行变更操作时会将数据变更到数据库,此时缓存中的数据会被清空,等执行查询操作时,访问缓存时没有数据,就会查询数据库中的数据,数据库中的数据就会返回到缓存中,通过更新机制能够保证缓存数据是最新数据。
缓存实现方式
1.在单实例的服务器下,可以在当前服务代码中通过添加HashMap集合实例来充当缓存
2.在多服务器中,需要通过缓存共享的形式来缓存数据,使用缓存服务器处理,缓存服务器的代表:Redis,memCache
Mybatis缓存
Mybatis缓存提供一级缓存和二级缓存
一级缓存是SqlSession级别的缓存、在操作数据库时需要构造SQLSession会话对象,对同一个对象中的数据可以使用到缓存,不同SqlSession之间的缓存时不共享的。
二级缓存是Mapper级别的缓存(namespace),不同的SqlSession之间的缓存时共享的,不同Mapper缓存是隔离的。
二者区别在于二级缓存范围更大,多个sqlSession可以共享一个Mapper的二级缓存区域
二级缓存使用步骤
- 在全局文件中开启二级缓存开关
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 将映射的pojo类实现序列化
public class Student implements Serializable
- 在配置文件中添加cache标签
<mapper namespace="com.tulun.dao.StudentMapper1">
<!--开启本Mapper的二级缓存-->
<cache></cache>
catch参数说明
<!--
cache参数说明:
flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
size:(引用次数)记录缓存对象的数量 默认值1024
readOnly(只读)参数为true:false 表示缓存的数据对象实例是不能被修改
eviction:缓存失效的策略 默认LRU
LRU:最近最少使用的:移除最长时间不使用的
FIFO:先进先出,按照缓存对象的顺序来淘汰
SOFT:软引用
WEAK:弱引用
-->
<cache eviction="LRU" readOnly="true" flushInterval="10000" size="12"></cache>
注: 在statement上配置useCache=“false”,禁用的是二级缓存
Mybatis的高级映射
一对一映射
在配置mapper.xml文件时
<!--resultType-->
<select id="getOrdersByNumber" parameterType="java.lang.String" resultType="com.tulun.pojo.Orders10">
select o.*,u.id "user.id",u.username "user.username",u.sex "user.sex",u.address "user.address" from orders o,user u where o.user_id = u.id and o.number= #{number};
</select>
<!--resultMap-->
<resultMap id="OrderUserResultMap" type="com.tulun.pojo.Orders10">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="note" property="note"/>
<!--user配置-->
<result column="u_id" property="user.id"/>
<result column="u_username" property="user.username"/>
<result column="u_sex" property="user.sex"/>
<result column="u_address" property="user.address"/>
</resultMap>
<select id="getOrdersByNumber" parameterType="java.lang.String" resultMap="OrderUserResultMap">
select o.*,u.id u_id,u.username u_username,u.sex u_sex,u.address u_address from orders o,user u where o.user_id = u.id and o.number= #{number};
</select>
resultMap提供的association配置一对一关系
一对多映射
<!--
使用resultMap中的Collection配置一对多关系
Collection:将关联查询的多条记录映射到集合兑现中
property:将关联查询的多条记录映射到的属性名:orders10s
ofType:执行映射的集合属性中的自定义类型
extends:继承主类性
-->
<resultMap id="ResultUserOrdersMapper" extends="ResultUserMapper" type="com.tulun.pojo.User10">
<!--Orders的结果集-->
<collection property="orders10s" columnPrefix="order_" ofType="com.tulun.pojo.Orders10">
<result 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"/>
</collection>
</resultMap>
<sql id="selectId">
u.id,
u.username,
u.sex,
u.address,
o.id order_id,
o.user_id order_user_id,
o.number order_number,
o.createtime order_createtime,
o.note order_note
</sql>
<select id="getUserOrdersById" parameterType="int" resultMap="ResultUserOrdersMapper">
select <include refid="selectId"/>
from user u ,orders o where u.id=o.user_id and u.id=#{uid};
</select>
多对多映射
多对多关系在java类实体中表示为,一个类中包含了集合为另一个类的属性。而这连个实体都需要包含对方的集合类的属性。在此不做过多介绍。
resultMap与resultType
resultMap:
使用association和Collection完成一对一和一对多高级映射(对结果又特殊要求的映射对象)
association:
作用:将关联查询的信息映射到一个pojo对象中
使用resultType无法将查询结果映射到pojo对象的pojo属性中时,选择resultMap来处理(association)
Collection:
将关联查询映射到一个List集合中
使用resultType无法将查询的结果映射到Pojo对象的List类型的pojo属性中时,使用resultMap的Collection标签来处理
resultType:
作用:将查询结果的SQL列名和pojo类的属性名完成一致性的映射
缺点:若SQL结果列名和pojo类属性名不一致,则无法自动完成映射
Mybatis中的延时加载
<settings>
<!--懒加载配置-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
resultMap实现高级映射(使用association和Colletion实现一对一和一对多映射),association和Collection具备延时加载功能
先从单表查询,需要时再从关联表去查询数据,这样能大大提高数据库的性能,单表查询要比多表查询快。
Mybatis逆向工程
Mybatis-Generator是Mybatis和Ibatis的代码生成器,可以生成简单CRUD操作的XML配置文件,Mapper的接口文件(Mapper接口)、实体类(POJO),可以有效的减少开发者的工作量,减少写手动写SQL的过程,减少出错风险
思想:需要先将数据库中表生成,包含字段,字段属性 可以来映射生成pojo类以及基本的Mapper.XML文件和Mapper接口文件。
Generator使用步骤
- 引入依赖
<!--generator的依赖-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
- 配置逆向工程的XML文件
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!--配置生成器-->
<generatorConfiguration>
<!--
context生成一组对象环境
id:必填,上下文的ID,用于在生成错误时提示
targetRuntime:
Mybatis3:用于生成mybatis 3.0以上版本的内容,包含XXXBySample
Mybatis3Simple:类似于mybatis3,但是不生成XXXBySample
-->
<context id="simple" targetRuntime="MyBatis3Simple">
<!--
数据库连接的信息:驱动类,连接的地址,账号,密码
-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="12345"
/>
<!--
生成pojo类信息
targetPackage:生成pojole类的包路径
targetProject:生成pojo类的位置
-->
<javaModelGenerator targetPackage="com.tulun.pojo" targetProject="src/main/java">
<!--
enableSubPackages:是否生成schema作为包的后缀
-->
<property name="enableSubPackages" value="false" />
<!--
trimStrings:从数据库返回的值清理前后的空格
-->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--
生成Mapper.xml文件的配置信息
-->
<sqlMapGenerator targetPackage="com.tulun.mapper" targetProject="src/main/resources"/>
<!--
生成Mapper的接口文件的配置
-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.tulun.mapper" targetProject="src/main/java"/>
<!--指定数据库的表-->
<table tableName="user" />
</context>
</generatorConfiguration>
- 通过Java代码生成相关代码
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
//读取配置文件的
File configFile = Resources.getResourceAsFile("mybatis-generator.xml");
// File configFile = new File("mybatis-generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}