目录
③测试端编写与UserMapper.xml映射的接口,UserMapper.xml中的namespace属性值是该接口的全类名、SQL片段的id属性值是接口中的方法名
第一部分 : 模仿Mybatis的自定义框架
1.1 原生JDBC
原生JDBC与数据库交互
加载数据库驱动
获取连接
定义SQL语句
获取预处理对象
设置参数
向数据库发出SQL请求
遍历并封装成实体对象
JDBC问题分析
- 数据库连接创建、释放资源频繁造成系统资源浪费,影响系统性能
- SQL语句在代码中硬编码,代码不易维护
- 预编译时向占位符传递参数时存在硬编码,不能自动传递,如果需要更改传递的参数,修改不易
- 结果集的解析存在硬编码,系统不易维护
1.2 解决思路
- 数据库连接池解决多次创建和释放连接对象时对IO资源的浪费
- 使用XML配置文件解决SQL语句及其它信息的硬编码
- 使用反射自动封装参数和结果集
1.3 设计
测试端
核心配置文件:
- sqlMapConfig.xml : 存放数据库连接池配置信息
- Mapper.xml : 存放SQL语句
将Mapper.xml文件的路径配置到sqlMapConfig.xml中
自定义持久层端
- 读取配置文件,并将文件以流的形式保存在流中
- 解析配置信息,并使用Java类来存储基本信息
- 使用工厂模式创建SqlSessionFactory接口,用来产生SqlSession实现对象
- 创建SqlSession接口,里面有相应的查询方法,如 : selectList,selectOne......
- 创建Executor接口,将JDBC代码进一步封装并在SqlSession的方法中调用Executor接口中的方法
1.4 测试端实现
①在resources中编写核心配置文件和SQL片段文件
sqlMapConfig.xml
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/zdy_mybatis?serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<!--将UserMapper.xml文件的路径引用到全局配置文件-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
UserMapper.xml
<mapper namespace="user">
<!--sql的唯一标识 : namespace.id组成 : statementId-->
<select id="findAll" resultType="com.huier.domain.entity.User">
select * from user
</select>
<select id="findByCondition" resultType="com.huier.domain.entity.User" paramterType="com.huier.domain.entity.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
②编写实体类
public class User {
private Integer id;
private String username;
private String 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 + '\'' +
'}';
}
}
1.5 自定义持久层端的实现
①读取配置文件,并将文件以流的形式保存在流中
- 创建Resource实体类
- 编写getResourceAsStream方法,获得传入的文件名,读取并存储文件信息到流中
import java.io.InputStream; public class Resources { public static InputStream getResourceAsStream(String srcPath){ //反射在四个阶段可以创建对象 /* 1.编译期间 2.类加载准备阶段 Resources.class.getClassLoader()获得类加载器,并读取文件信息 3.类加载阶段 4.运行阶段 */ InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(srcPath); return inputStream; } }
InputStream resourceAsStream = Resources.getResourceAsStream(path);//将核心配置文件以及SQL配置文件皆以流的形式存储到内存中,方便后续解析
②解析配置信息,并使用Java类来存储基本信息
- 创建Configuration类存储全局配置信息
- 创建MapperStatement类存储SQL信息,每一条SQL语句都是一个MapperStatement对象
- 创建XMLConfigBuilder类解析sqlMapConfig.xml配置文件并封装成JavaBean对象
- 创建XMLMapperBuilder类解析Mapper.xml配置文件并封装成JavaBean对象
- 创建SqlSessisonFactoryBuilder类完成文件解析、封装并生成SqlSessisonFactory实现类对象
Configuration类 : 配置类,存储数据源信息和SQL语句
@Data//lombok插件自动生成get set方法等
public class Configuration {
private DataSource dataSource;//数据源
/**
* @Author HuiEr
* @Date 2022/10/9 14:51
* @Version 2020
* @Description key : statementId[namespace.id]
*/
private Map<String,MappedStatement> statementMap
= new HashMap<String, MappedStatement>();
}
MapperStatement : 存储SQL语句信息
@Data
@AllArgsConstructor
public class MappedStatement {
private String id;//唯一标识
private String resultType;//结果类型
private String paramType;//参数类型
private String sql;//sql语句
}
SqlSessisonFactoryBuilder : 解析、封装JavaBean对象,生成SqlSessisonFactory对象
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream in) throws DocumentException {
//解析xml 封装Configuration
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(in);//生成Configuration对象
Configuration configuration = xmlConfigBuilder.parseConfig();//为对象的数据源属性赋值
//创建sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
XMLConfigBuilder : 解析核心配置文件并封装成Configuration对象
public class XMLConfigBuilder {
private Configuration configuration;//核心配置类
private InputStream in;//xml的流文件
public XMLConfigBuilder(InputStream in){
this.in = in;
configuration = new Configuration();
}
/**
* @Author HuiEr
* @Date 2022/10/9 15:03
* @Version 2020
* @Description 将配置文件解析并封装成javaBean
*/
public Configuration parseConfig() throws DocumentException {
Document document = new SAXReader().read(in);
//<configuration>
Element rootElement = document.getRootElement();
List<Element> dataSourceList = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : dataSourceList) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name, value);
}
//使用连接池
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.getProperty("driverClass"));
dataSource.setUrl(properties.getProperty("jdbcUrl"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(dataSource);
//解析mapper.xml
List<Element> mappers = rootElement.selectNodes("//mapper");
for (Element mapper : mappers) {
String resource = mapper.attributeValue("resource");
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration,resourceAsStream);
xmlMapperBuilder.parse();
}
return configuration;
}
}
XMLMapperBuilder : 解析配置Mapper文件,将每一个SQL语句封装成MapperStatement对象
public class XMLMapperBuilder {
private Configuration configuration;//核心配置类
private InputStream in;
public XMLMapperBuilder(Configuration configuration,InputStream in){
this.configuration = configuration;
this.in = in;
}
public void parse() throws DocumentException {
Document document = new SAXReader().read(in);
Element rootElement = document.getRootElement();//<mapper>
String namespace = rootElement.attributeValue("namespace");
List<Element> selectNodes = rootElement.selectNodes("//select");
for (Element selectNode : selectNodes) {
String id = selectNode.attributeValue("id");
String resultType = selectNode.attributeValue("resultType");
String paramterType = selectNode.attributeValue("paramterType");
String sql = selectNode.getTextTrim();
String statementId = namespace+"."+id;
MappedStatement mappedStatement = new MappedStatement(id,resultType,paramterType,sql);
configuration.getStatementMap().put(statementId, mappedStatement);
}
}
}
SqlSessisonFactoryBuilder接口 : 生产SqlSession对象
public interface SqlSessionFactory {
public SqlSession openSession();
}
SqlSessisonFactoryBuilder接口实现类 : DefultSqlSessionFactory类
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;//已经封装好核心配置类信息和SQL信息
public DefaultSqlSessionFactory(Configuration configuration){
this.configuration = configuration;
}
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
SqlSessison接口 : 与数据库连接查询的基础方法,如selectOne,selectList等
参数说明 : statementId : 定位SQL,由*Mapper.xml的namespace.id组成;Object...params : 传入的参数,这里使用实体类作为参数直接查询
public interface SqlSession {
//查询所有
public <T> List<T> selectList(String statementId,Object...params) throws Exception;
//根据条件查询单个
public <T> T selectOne(String statementId,Object...params) throws Exception;
}
SqlSessison接口实现类 : DefaultSqlSessison
具体实现了接口的方法,在这里并没有直接在重写方法体中编写代码实现操作,而是调用Executor接口的方法来进一步实现与数据库的交互功能,可以极大的明确每个类的功能、方便维护、更改
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
public <T> List<T> selectList(String statementId, Object... params) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
List<Object> query = simpleExecutor.query(configuration, configuration.getStatementMap().get(statementId), params);
return (List<T>) query;
}
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (objects.size() == 1) {
return (T) objects.get(0);
} else {
throw new RuntimeException("查询结果为空或者返回结果过多");
}
}
public <T> T getMapper(Class<?> mapperClass) {
//使用JDK动态代理
Object instance = Proxy.newProxyInstance(
DefaultSqlSession.class.getClassLoader(),
new Class[]{mapperClass},
new InvocationHandler() {
public Object invoke(Object proxy,//当前代理对象的引用
Method method,//当前被调用方法的引用
Object[] args) throws Throwable {
//准备参数 将接口全路径和namespace、方法名和id保持一致,可以拼装成statementId
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){//类型是否有泛型
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId, args);
}
});
return (T) instance;
}
}
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);//读取流中的文件,并使用dom4j技术解析,并存储到JavaBean中,返回工厂实例对象
③创建SqlSession对象,并调用方法进行查询
- 使用SqlSessionFactory对象中的openSession()方法创建SqlSession对象
- 调用SqlSession中的方法与数据库进行交互查询
Executor接口 : 定义了具体的与数据库交互的操作
参数说明 : Configuration configuration : 传入配置信息和SQL语句; MappedStatement mappedStatement : 获得本次查询的具体的SQL语句;Object...params : 查询时的参数,这里就是实体类对象
public interface Executor {
public <T> List<T> query(Configuration configuration,
MappedStatement mappedStatement,
Object...params) throws Exception;
}
Executor接口实现类 : SimpleExecutor
public class SimpleExecutor implements Executor {
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
Connection connection = configuration.getDataSource().getConnection();
String sql = mappedStatement.getSql();
//替换并使用?作为占位符
BoundSql boundSql = getBoundSql(sql);
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
String paramType = mappedStatement.getParamType();
Class<?> paramTypeClass = getClassType(paramType);
List<ParameterMapping> mappings = boundSql.getMappings();
for(int i = 0;i < mappings.size();i++){
String content = mappings.get(i).getContent();//字段名
//反射
Field field = paramTypeClass.getDeclaredField(content);
field.setAccessible(true);
Object o = field.get(params[0]);
preparedStatement.setObject(i+1, o);
}
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resClass = getClassType(resultType);
ArrayList<Object> objects = new ArrayList();
while (resultSet.next()){
Object o = resClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();//元数据
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i);//字段名
Object object = resultSet.getObject(columnName);
//使用反射
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,object);
}
objects.add(o);
}
return (List<T>) objects;
}
private Class<?> getClassType(String paramType) throws ClassNotFoundException {
if(paramType != null){
Class<?> aClass = Class.forName(paramType);
return aClass;
}
return null;
}
private BoundSql getBoundSql(String sql) {
//标记处理类 : 配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
//解析出来的sql
String parse = parser.parse(sql);
//解析出来的参数名称
List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
return new BoundSql(parse, parameterMappings);
}
}
getBoundSql(String sql) :解析原有的SQL并使用?作为占位符,返回BoundSql对象
private BoundSql getBoundSql(String sql) {
//标记处理类 : 配置标记解析器来完成对占位符的解析处理工作
ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
//解析出来的sql
String parse = parser.parse(sql);
//解析出来的参数名称
List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
return new BoundSql(parse, parameterMappings);
}
BoundSql类 : 存储使用?作为占位符的SQL语句、替换前{}中的参数名称
归纳总结上述过程 : 1.以流的形式加载配置文件到内存中;2.解析流形成JavaBean对象,生成工厂对象;3.使用工厂对象创建SqlSession对象使用基础方法与数据库进行交互查询
完整测试
String path = "sqlMapConfig.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(path);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
User user = new User();
user.setId(1);
user.setUsername("张三");
User selectUser = sqlSession.selectOne("user.selectOne", user);
List<Object> objects = sqlSession.selectList("user.selectList");
for (Object object : objects) {
System.out.println(object);
}
System.out.println(selectUser);
上述过程已经完成了一个简易的持久层框架,但是在调用方法进行查询时依旧存在硬编码
String path = "sqlMapConfig.xml";//xml文件地址
InputStream resourceAsStream = Resources.getResourceAsStream(path);//转换成流
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);//解析生成JavaBean
SqlSession sqlSession = factory.openSession();//获取SqlSession对象
User user = new User(1,'张三');
//调用查询单个方法
sqlSession.selectOne("User.selectOne",user);//仍然存在硬编码,需要手动拼接statementId去传入
/*
statementId = UserMapper.xml中的namespace属性值.UserMapper.xml中SQL语句的id
*/
对此,我们可以将整个Mapper.xml文件封装成一个相应的类,将namespace和类的全类名相同,每一个SQL语句的id和方法名对应,这样我们可以通过反射技术进行自动拼接。但是我们这个类需要有实体方法吗?检查这个类的作用仅仅只是作为一个标识符来定位具体的SQL片段,所以我们将这其定义成一个接口
自定义持久层框架的改进
使用代理模式对方法进行增强
①在SqlSession接口中添加方法
//生成动态代理对象
public <T> T getMapper(Class<?> mapperClass);
②在DefaultSqlSession类中具体实现
public <T> T getMapper(Class<?> mapperClass) {
//使用JDK动态代理
Object instance = Proxy.newProxyInstance(
DefaultSqlSession.class.getClassLoader(),
new Class[]{mapperClass},
new InvocationHandler() {
public Object invoke(Object proxy,//当前代理对象的引用
Method method,//当前被调用方法的引用
Object[] args) throws Throwable {
//准备参数 将接口全路径和namespace、方法名和id保持一致,可以拼装成statementId
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
Type genericReturnType = method.getGenericReturnType();
if(genericReturnType instanceof ParameterizedType){//类型是否有泛型
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId, args);
}
});
return (T) instance;
}
③测试端编写与UserMapper.xml映射的接口,UserMapper.xml中的namespace属性值是该接口的全类名、SQL片段的id属性值是接口中的方法名
public interface UserMapper {
public List<User> findAll() throws Exception;
public User findByCondition(User user) throws Exception;
}
<mapper namespace="com.huier.mapper.UserMapper">
<!--sql的唯一标识 : namespace.id组成 : statementId-->
<select id="findAll" resultType="com.huier.domain.entity.User">
select * from user
</select>
<select id="findByCondition" resultType="com.huier.domain.entity.User" paramterType="com.huier.domain.entity.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
③测试动态代理的方式实现查询
String path = "sqlMapConfig.xml";//xml文件地址
InputStream resourceAsStream = Resources.getResourceAsStream(path);//转换成流
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);//解析生成JavaBean
SqlSession sqlSession = factory.openSession();//获取SqlSession对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);//获取增强类
List<User> all = mapper.findAll();//直接调用方法即可
这样就实现了一个简易的持久层框架,框架的底层大量使用了反射相关技术,将原有JDBC的硬编码通过编写文件的方式解决、手动映射则用反射代替,大大降低了代码之间的耦合度,降低了编写重复代码的频率,提高了效率。
第二部分 : Mybatis的基本使用
2.1 快速使用
2.1.1 开发步骤
- 基于Maven工程实现快速体验,导入Mybatis坐标
<!--mysql驱动,Mybatis实质还是对JDBC的封装需要mysql的支持--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version> </dependency> <!--mybatis坐标--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <!--测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency>
- 创建实体类
public class User implements Serializable { private Integer userId; private String userName; private Integer userAge; public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public Integer getUserAge() { return userAge; } public void setUserAge(Integer userAge) { this.userAge = userAge; } @Override public String toString() { return "User{" + "userId=" + userId + ", userName='" + userName + '\'' + ", userAge=" + userAge + '}'; } }
- 创建数据库与实体类对应的表
- 编写核心配置文件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> <!--数据源环境--> <environments default="development"><!--指定默认的环境名称--> <environment id="development"><!--指定当前环境的名称--> <transactionManager type="JDBC"/><!--指定事务管理类型是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/UserMapper.xml"/> </mappers> </configuration>
- 编写接口UserMapper和SQL文件UserMapper.xml
public interface UserMapper { /* Mybatis代理的四要素: 1.namespace必须是接口的全限定名 2.方法名 == id值 3.参数类型要一致 4.返回类型要一致 */ List<User> findAll() throws IOException; }
<?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"> <!--namespace和接口全类名匹配--> <mapper namespace="mapper.UserMapper"> <!--id和接口方法名匹配--> <select id="findAll" resultType="user"> <include refid="selectUser"></include> </select> </mapper>
- 测试类测试
//1.获得核心配置文件 InputStream resource = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.获得sqlSession工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource); //3.获得session回话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //获得UserMapper的代理对象,由MyBatis代理实现实例化对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.findAll(); System.out.println(users);
2.1.2 Mybatis的增删改查注意点
- 插入insert操作 :①使用insert标签;②指定参数类型parameterType = "参数类型";③SQL语句中使用#{实体属性名}作为占位符;④插入操作调用的API是sqlSession.insert("命名空间.id",实体对象)或代理对象的方式;⑤手动提交事务,sqlSession.commit()
- 修改update操作 : ①使用update标签;②手动提交事务,sqlSession.commit()
- 删除delete操作 : ①使用delete标签;②SQL语句中如果只有一个参数#{任意字符串}即可;③手动提交事务,sqlSession.commit()
- 查询select操作 : ①使用select标签;②指定结果类型resultType = "结果类型"
2.1.3 基本Mybatis核心配置文件分析
<?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"><!--xml文件约束头-->
<configuration><!--根标签-->
</configuration>
2.1.4 核心配置文件标签分析
MyBatis核⼼配置⽂件层级关系
- configuration
- properties : 引入外部文件
- settings : 配置
- typeAliases : 实体类别名
- typeHeadlers
- objectFactory
- plungins
- enviroments : 环境
- enviroment
- transactionManager
- dataSource
- enviroment
- databasedProvider
- mappers : 引入Mapper.xml文件
Mybatis常用配置解析
①environment,数据库环境的配置,支持多环境配置
<environments default="development"><!--指定默认的环境名称-->
<environment id="development"><!--指定当前环境的名称-->
<transactionManager type="JDBC"/><!--指定事务管理类型是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>
事务管理器类型有两种
- JDBC : 直接使用JDBC的提交和回滚设置,它依赖于数据源得到的连接来管理事务作
⽤域 - MANAGED
数据源类型有三种
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
- POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
- JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配
置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。
②mapper,该标签的作用是加载映射,加载方式有如下几种
<mapper resource="相对路径"/>
<mapper url="绝对路径"/>
<mapper class="全类名"/>
<!--注意 : 如果使用包名来映射xml文件,则mappper接口的路径要和xml文件在同一个文件夹中-->
<package name="org.mybatis.builder"/>
2.1.5 Mybatis相应API介绍
SqlSession⼯⼚构建器SqlSessionFactoryBuilder
常⽤API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核⼼⽂件的输⼊流的形式构建⼀个SqlSessionFactory对象
String resource = "核心配置文件";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);
SqlSession工厂对象SqlSessionFactory
SqlSessionFactory有多个方法创建SqlSession实例,常用
- openSession(),增删改时需要手动提交事务
- openSession(boolean),传入true则表示,自动提交事务
SqlSession会话对象
执行语句的主要方法
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
操作事务的方法
void commit()
void rollback()
2.2 Mybatis的Dao层实现
- 传统方式
- 代理类
第三部分 : Mybatis配置文件深入
3.1 核心配置文件SqlMapConfig.xml
3.1.1 Mybatis核心配置文件
①Properties标签
实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的properties⽂件
②typeAliases标签
类型别名是为Java 类型设置⼀个短的名字,自动扫描该包下的类
第四部分 : Mybatis缓存
4.1 一级缓存
总结 : 查询操作首先从缓冲池中获取答案,如果没有则查询后将答案存入缓冲池;增删改操作都将清空缓冲池,防止数据的不合法
4.2 二级缓存
二级缓存的原理和一级缓存相同,第一次查询时,会将数据放入缓存中,第二次查询时则会直接从缓冲区中获取,在进行增删改操作后会清空缓存区,但是和一级缓存不同的时,缓存的范围会变得更大。一级缓存是以SqlSession为域对象,而二级缓存是以Mapper.xml文件的namespace作为域对象
二级缓存构建在一级缓存之上,在收到查询请求时,Mybatis首先会查询二级缓存,若二级缓存未命名,再去查询一级缓存,一级缓存没有,再查询数据库
与一级缓存不同,二级缓存和具体的命名空间相绑定,一个Mapper中只有一个Cache,相同Mapper中的MapperStatement(将SQL语句封装成一个MapperStatement对象)公用一个Cache,一级缓存则是和SqlSession相绑定
如何开启二级缓存?
- 开启二级缓存 : 和一级缓存不同的是,二级缓存需要手动开启,在核心配置文件添加
<!--开启⼆级缓存--> <settings> <setting name="cacheEnabled" value="true"/> </settings>
需要在相应的Mapper.xml文件中开启缓存
<!--开启⼆级缓存--> <cache></cache>
这里可以使用Mybatis默认的缓存功能类,也可以使用自定义缓存。我们不写type就使用Mybatis默认的缓存,也可以去实现Cache接口来自定义缓存
开启了⼆级缓存后,还需要将要缓存的pojo实现Serializable接⼝,为了将缓存数据取出执⾏反序列化操作,因为⼆级缓存数据存储介质多种多样,不⼀定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接⼝useCache和flushCache
mybatis中还可以配置userCache和flushCache等配置项,userCache是⽤来设置是否禁⽤⼆级缓 存的,在statement中设置useCache=false可以禁⽤当前select语句的⼆级缓存,即每次查询都会发出 sql去查询,默认情况是true,即使⽤⼆级缓存<select useCache="false"> </select>
设置flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新
<select flushCache="true" useCache="false"> </select>
一般情况下我们应该在进行增删改操作后提交事务刷新缓存,所以我们直接使用默认的配置即可