Mybatis
第⼀部分:⾃定义持久层框架
1.1 分析JDBC操作问题
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执⾏查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
}catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC问题与解决思路:
原始jdbc开发存在的问题如下:
-
获取连接部分
- 数据库配置信息硬编码
- 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
解决思路
使用配置文件
使用连接池
-
sql编写部分
- Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。
- 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。
解决思路
使用配置文件
-
结果集封装部分
- 手动封装结果集, 操作繁琐. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便
解决思路
使用 反射 或 内省 ,将⾃动将实体与表进⾏属性与字段的⾃动映射
1.2 ⾃定义框架设计
使⽤端:
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.raphuscucullatus</groupId>
<artifactId>IPersistence_test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.raphuscucullatus</groupId>
<artifactId>IPersistence</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
提供核⼼配置⽂件:
sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
<configuration>
<!数据库配置信息 -->
<dataSource>
<properties name="driverClass" value="com.mysql.cj.jdbc.Driver"></properties>
<properties name="jdbcUrl" value="jdbc:mysql://localhost:3306/custom_mybatis"></properties>
<properties name="username" value="root"></properties>
<properties name="password" value="088.5741"></properties>
</dataSource>
<!mapper.xml映射配置文件路径 -->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
Mapper.xml : sql语句的配置⽂件信息
<mapper namespace="com.raphuscucullatus.dao.UserDao">
<!--
statementId:
namespace.id 是SQL语句在当前工程中的唯一标识
-->
<select id="findAll" resultType="com.raphuscucullatus.pojo.User">
select id,username from user
</select>
<select id="findByNameAndId" paramType="com.raphuscucullatus.pojo.User" resultType="com.raphuscucullatus.pojo.User">
select id,username from user where id = #{id} and username = #{username}
</select>
</mapper>
pojo
package com.raphuscucullatus.pojo;
/**
* 用户类实体
* @author raphus cucullatus
* @version 2021/9/29 13:49
* @since JDK11
*/
public class User {
private Integer id;
private String username;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + username + '\'' +
'}';
}
}
Dao
package com.raphuscucullatus.dao;
import com.raphuscucullatus.pojo.User;
import java.util.List;
/**
* @author raphus cucullatus
* @version 2021/10/1 7:48
* @since JDK11
*/
public interface UserDao {
/**
* 查找所有用户
* @return
*/
public List<User> findAll() throws Exception;
/**
* 按条件查找用户
* @return
*/
public User findByNameAndId(User userCondition) throws Exception;
}
测试
package com.raphuscucullatus.test;
import com.raphuscucullatus.dao.UserDao;
import com.raphuscucullatus.io.Resources;
import com.raphuscucullatus.pojo.User;
import com.raphuscucullatus.sqlsession.SqlSession;
import com.raphuscucullatus.sqlsession.SqlSessionFactory;
import com.raphuscucullatus.sqlsession.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @author raphus cucullatus
* @version 2021/9/29 14:16
* @since JDK11
*/
public class IPersistenceTest {
@Test
public void testGetResourcesAsStream()throws Exception{
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User userCondition = new User();
userCondition.setId(1);
userCondition.setUsername("lucy");
// User user = sqlSession.selectOne("user.selectOne", userCondition);
// List<User> list = sqlSession.selectList("user.selectList");
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findByNameAndId(userCondition);
List<User> userList = userDao.findAll();
System.out.println("查询用户结果:"+user);
System.out.println("userList中的数据为"+userList);
}
}
框架端:
框架端相关依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<! jaxen是一个开源的XPath库。Jaxen是一个Java编写的开源的XPath库。
这是适应多种不同的对象模型,包括DOM,XOM,dom4j和JDOM。 -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
##### 1.读取配置⽂件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
(1) Configuration : 存放数据库基本信息、 Map<唯⼀标识, Mapper> 唯⼀标识: namespace + “.”+ id
package com.raphuscucullatus.pojo;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 用于存放核心配置文件信息
* @author raphus cucullatus
* @version 2021/9/29 14:33
* @since JDK11
*/
public class Configuration {
/*** 数据源*/
private DataSource dataSource;
/** * key: statementid value: MappedStatement对象*/
Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
public DataSource getDataSource() {return dataSource;}
public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}
public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap; }
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap){this.mappedStatementMap = mappedStatementMap;}
}
(2) MappedStatement: sql语句、 statement类型、输⼊参数java类型、输出参数java类型
package com.raphuscucullatus.pojo;
/**
* 用于存放映射配置文件信息
* @author raphus cucullatus
* @version 2021/9/29 14:28
* @since JDK11
*/
public class MappedStatement {
/** * Id标识 */
private String id;
/** * sql语句 */
private String sql;
/** * 参数类型 */
private String paramType;
/** * 返回值类型 */
private String returnType;
public String getId() {return id; }
public void setId(String id) {this.id = id;}
public String getSql() { return sql; }
public void setSql(String sql) {this.sql = sql; }
public String getParamType() { return paramType; }
public void setParamType(String paramType) {this.paramType = paramType;}
public String getReturnType() {return returnType;}
public void setReturnType(String returnType) {this.returnType = returnType;}
}
2.解析配置⽂件
创建sqlSessionFactoryBuilder类:
⽅法: sqlSessionFactory build():
package com.raphuscucullatus.sqlsession;
import com.raphuscucullatus.config.XmlConfigBuilder;
import com.raphuscucullatus.pojo.Configuration;
import org.dom4j.DocumentException;
import java.io.InputStream;
/**
* @author raphus cucullatus
* @version 2021/9/29 14:48
* @since JDK11
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
// 1. 使用dom4j解析配置文件, 将解析出来的内容封装到Configuration中
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
// 2. 创建SqlSessionFactory对象: 生产sqlSession
SqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中
package com.raphuscucullatus.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.raphuscucullatus.io.Resources;
import com.raphuscucullatus.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/**
* 用于解析核心配置文件
* @author raphus cucullatus
* @version 2021/9/29 14:53
* @since JDK11
*/
public class XmlConfigBuilder {
/*** 用于存放核心配置文件信息 */
private Configuration configuration;
public XmlConfigBuilder(){this.configuration = new Configuration();}
/** * 使用dom4j解析配置信息 封装到Configuration中* @return */
public Configuration parseConfig(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
//获取核心配置文件跟标签configuration
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//properties");
Properties properties = new Properties();
for (Element element : list) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(properties.getProperty("jdbcUrl"));
dataSource.setUsername(properties.getProperty("username"));
dataSource.setPassword(properties.getProperty("password"));
dataSource.setDriverClassName(properties.getProperty("driverClass"));
configuration.setDataSource(dataSource);
//mapper.xml解析: 获取路径 加载字节流 dom4j解析
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
XmlMappedBuilder xmlMappedBuilder = new XmlMappedBuilder(configuration);
xmlMappedBuilder.parse(resourceAsStream);
}
return configuration;
}
}
第⼆:创建SqlSessionFactory的实现类DefaultSqlSession
3.创建SqlSessionFactory:
⽅法: openSession() : 获取sqlSession接⼝的实现类实例对象
package com.raphuscucullatus.sqlsession;
/**
* @author raphus cucullatus
* @version 2021/9/2914:52
* @since JDK11
*/
public interface SqlSessionFactory {
SqlSession openSession();
}
默认实现类
package com.raphuscucullatus.sqlsession;
import com.raphuscucullatus.pojo.Configuration;
/**
* @author raphus cucullatus
* @version 2021/9/29 18:04
* @since JDK11
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration){
this.configuration=configuration;
}
@Override
public SqlSession openSession() {return new DefaultSqlSession(configuration);}
}
4.创建sqlSession接⼝及实现类:主要封装crud⽅法
⽅法: selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作
package com.raphuscucullatus.sqlsession;
import java.util.List;
/**
* @author raphus cucullatus
* @version 2021/9/2918:36
* @since JDK11
*/
public interface SqlSession {
/** * 查询所有 */
<T> List<T> selectList(String statementId,Object... param) throws Exception;
/*** 查询单个 */
<T> T selectOne(String statementId,Object... param) throws Exception;
/**
* 为Mapper接口生成实现类
* @param mapperClass
* @param <T>
* @return
*/
<T> T getMapper(Class<?> mapperClass);
}
默认实现类
package com.raphuscucullatus.sqlsession;
import com.raphuscucullatus.pojo.Configuration;
import com.raphuscucullatus.pojo.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
/**
* @author raphus cucullatus
* @version 2021/9/29 18:37
* @since JDK11
*/
public class DefaultSqlSession implements SqlSession{
/**
* 配置文件数据
*/
private final Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration=configuration;
}
@Override
public <T> List<T> selectList(String statementId, Object... params) throws Exception {
//通过调用 simpleExecutor的query方法 实现查询
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
List<T> list = simpleExecutor.query(configuration, mappedStatement, params);
return list;
}
@Override
public <T> T selectOne(String statementId, Object... param) throws Exception {
List<T> list = selectList(statementId, param);
if (list.size()==1){
return list.get(0);
}else{
throw new RuntimeException("查询结果为空,或者结果不止一个");
}
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用动态代理为生成Dao层接口实现类(也就是myBatis中的Mapper的实现类) 形参: 类加载器 被代理类接口[数组] handler
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[] {mapperClass}, new InvocationHandler() {
/* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象的方法的形参*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//底层还是JDBC代码 //事项根据不同情况选择执行不同方法的功能
// 准备参数 1.statementId SQL语句的唯一标识: namespace.id = 接口全限定名.方法名(按这种方式命名就可以不用在此处在去读取一遍配置文件来获取statementId)
String methodName = method.getName();//方法名
String className = method.getDeclaringClass().getName();//类全限定名
String statementId = className + "." + methodName;
//获取被调用方法的返回值类型 Type:所有类型的通用超级接口。 这些包括原始类型、参数化类型、数组类型、类型变量和原始类型。
Type genericReturnType = method.getGenericReturnType();
//是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
//List<Object> list = selectList(statementId);
//return list;
return selectList(statementId);
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
}
执行器
package com.raphuscucullatus.sqlsession;
import com.raphuscucullatus.pojo.Configuration;
import com.raphuscucullatus.pojo.MappedStatement;
import java.sql.SQLException;
import java.util.List;
/**
* sql执行器接口 用于封装JDBC代码
* @author raphus cucullatus
* @version 2021/9/2919:47
* @since JDK11
*/
public interface Executor {
/**
* 执行器
* @param configuration 核心配置文件
* @param mappedStatement 映射配置文件
* @param params sql执行参数
* @param <T> 泛型
* @return 泛型集合
*/
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
默认实现
package com.raphuscucullatus.sqlsession;
import com.raphuscucullatus.Utils.GenericTokenParser;
import com.raphuscucullatus.Utils.ParameterMapping;
import com.raphuscucullatus.Utils.ParameterMappingTokenHandler;
import com.raphuscucullatus.config.BoundSql;
import com.raphuscucullatus.pojo.Configuration;
import com.raphuscucullatus.pojo.MappedStatement;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
/**
* 简单执行器
* @author raphus cucullatus
* @version 2021/9/29 19:54
* @since JDK11
*/
public class SimpleExecutor implements Executor{
/**
* @param configuration 核心配置文件
* @param mappedStatement 映射配置文件
* @param param userCondition
* @param <T>
* @return
* @throws Exception
*/
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... param) throws Exception {
//1.注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
//2.获取Sql 即:select id,username form user where id = #{id} and username = #{username}
//解析后Sql 即:select id,username form user where id = ? and username = ?
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
//3.获取预处理对象: preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getParseSql());
//4设置参数
//通过映射配置文件获取参数类型全路径名,进而获取其运行时类
String paramType = mappedStatement.getParamType();
Class<?> paramTypeClass = getClassType(paramType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
//反射获取运行时类与content同名字段
Field declaredField = paramTypeClass.getDeclaredField(content);
declaredField.setAccessible(true);
//获取param[0]对象中与content同名字段的值
Object o = declaredField.get(param[0]);
//设置占位符的值
preparedStatement.setObject(i+1,o);
}
//5执行sql
ResultSet resultSet = preparedStatement.executeQuery();
//6封装结果集
//通过反射获取结果集运行时类 并创建该类型对象
String returnType = mappedStatement.getReturnType();
Class<?> returnTypeClass = Class.forName(returnType);
ArrayList<Object> list = new ArrayList<>();
while (resultSet.next()){
Object o = returnTypeClass.newInstance();
//结果集元数据(列数,列名,等)
ResultSetMetaData metaData = resultSet.getMetaData();
//遍历所有列 (数据库列数从1开始)
for (int i = 0; i < metaData.getColumnCount(); i++) {
//获取列名
String columnName = metaData.getColumnName(i + 1);
//获取该列的值
Object columnValue = resultSet.getObject(columnName);
//获取该列所在表表和实体类对应关系(反射或内省)
//反射
//内省
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, returnTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
//将字段值封装至返回类型对象o中
writeMethod.invoke(o,columnValue);
}
list.add(o);
}
return (List<T>) list;
}
private Class<?> getClassType(String paramType) throws ClassNotFoundException {
if (paramType!=null) {
return Class.forName(paramType);
}
return null;
}
/**
* 对#{}进行解析: 1将#{}替换为? 2解析出#{}内的值
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql){
//标记处理类: 配合标记解析器来完成对占位符的处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析后的sql
String parseSql = genericTokenParser.parse(sql);
//#{}中解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
}
package com.raphuscucullatus.config;
import com.raphuscucullatus.Utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
/**
* 封装解析出的sql以及#{}或${}中的参数
* @author raphus cucullatus
* @version 2021/9/29 21:06
* @since JDK11
*/
public class BoundSql {
private String parseSql;
private List<ParameterMapping> parameterMappingList = new ArrayList<>();
public BoundSql(String parseSql, List<ParameterMapping> parameterMappingList) {
this.parseSql = parseSql;
this.parameterMappingList = parameterMappingList;
}
public String getParseSql() {
return parseSql;
}
public void setParseSql(String parseSql) {
this.parseSql = parseSql;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}
涉及到的设计模式:
Builder构建者设计模式、⼯⼚模式、代理模式
第⼆部分: Mybatis相关概念
官网
2.1 对象/关系数据库映射(ORM)
ORM全称Object/Relation Mapping:表示对象-关系映射的缩写. ORM完成⾯向对象的编程语⾔到关系数据库的映射。当ORM框架完成映射后,程序员既可以利⽤⾯向对象程序设计语⾔的简单易⽤性,⼜可以利⽤关系数据库的技术优势。 ORM把关系数据库包装成⾯向对象的模型。 ORM框架是⾯向对象设计语⾔与关系数据库发展不同步时的中间解决⽅案。采⽤ORM框架后,应⽤程序不再直接访问底层数据库,⽽是以⾯向对象的⽅式来操作持久化对象,⽽ORM框架则将这些⾯向对象的操作转换成底层SQL操作。 ORM框架实现的效果:把对持久化对象的保存、修改、删除等操作,转换为对数据库的操作
2.2 Mybatis简介
MyBatis是⼀款优秀的基于ORM的半⾃动轻量级持久层框架,它⽀持定制化SQL、存储过程以及⾼级映射。 MyBatis避免了⼏乎所有的JDBC代码和⼿动设置参数以及获取结果集。 MyBatis可以使⽤简单的XML或注解来配置和映射原⽣类型、接⼝和Java的POJO (Plain Old Java Objects,普通⽼式Java对 象)为数据库中的记录。
2.3 Mybatis历史
原是apache的⼀个开源项⽬iBatis, 2010年6⽉这个项⽬由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下, ibatis3.x正式更名为Mybatis ,代码于2013年11⽉迁移到Github。iBATIS⼀词来源于“internet”和“abatis”的组合,是⼀个基于Java的持久层框架。 iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
2.4 Mybatis优势
Mybatis是⼀个半⾃动化的持久层框架,对开发⼈员开说,核⼼sql还是需要⾃⼰进⾏优化, sql和java编码进⾏分离,功能边界清晰,⼀个专注业务,⼀个专注数据。
第三部分: Mybatis基本应⽤
3.1 快速入门
3.1.1 开发步骤:
①添加MyBatis的坐标
<!-https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
②创建user数据表
③编写User实体类
package com.raphuscucullatus.pojo;
/**
* user表实体类
*
* @author raphus cucullatus
* @version 2021/10/1 19:50
* @since JDK11
*/
public class User {
private int id;
private String username;
//并生成 get set 以及 tostring 方法
}
④编写映射⽂件UserMapper.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="user">
<select id="findAll" resultType="com.raphuscucullatus.pojo.User">
select * from User
</select>
</mapper>
⑤编写核⼼⽂件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"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/custom_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="088.5741"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
⑥编写测试类
@Test
public void testQuickStart() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("user.findAll");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
3.1.2 MyBatis的增删改查操作
pojo
package com.raphuscucullatus.pojo;
public class User {
private int id;
private String username;
//以及get set tostring方法
}
映射配置文件
<?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="user">
<select id="findAll" resultType="com.raphuscucullatus.pojo.User">
select id,username from user
</select>
<select id="saveUser" parameterType="com.raphuscucullatus.pojo.User">
insert into user values (${id},#{username},null,null)
</select>
<select id="updateUser" parameterType="com.raphuscucullatus.pojo.User">
update user set username=#{username} where id = #{id}
</select>
<select id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{id}
</select>
</mapper>
测试
@Test
public void testQuickStart() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("user.findAll");
for (User user : list) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void testSaveUser() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("狗蛋");
user.setId(5);
sqlSession.insert("user.saveUser",user);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdateUser() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSession();
User user = new User();
user.setUsername("铁柱");
user.setId(5);
sqlSession.update("user.updateUser",user);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDeleteUser() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSession();
int delete = sqlSession.delete("user.deleteUser", 5);
sqlSession.commit();
sqlSession.close();
}
3.1.3 MyBatis的映射⽂件概述
概述:
1. MyBatis核心配置文件常⽤配置解析
- Properties标签
实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的properties⽂件
-
typeAliases标签
设置别名前
<select id="findAll" resultType="com.raphuscucullatus.pojo.User"> select id,username from user </select>
设置别名后
类型别名是为Java 类型设置⼀个短的名字。原来的类型名称配置如下
<typeAliases> <typeAlias type="com.raphuscucullatus.domain.User" alias="User"/> </typeAliases>
配置typeAliases,为com.raphuscucullatus.domain.User定义别名为user
<select id="findAll" resultType="User"> select id,username from user </select>
上⾯我们是⾃定义的别名, mybatis框架已经为我们设置好的⼀些常⽤的类型的别名
- environments标签
数据库环境的配置,⽀持多环境配置
其中,**事务管理器(transactionManager)**类型有两种:
-
JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作⽤域。
-
MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。
其中,**数据源(dataSource)**类型有三种:
-
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
-
POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
-
JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。
- mapper标签
该标签的作⽤是加载映射的,加载⽅式有如下⼏种:
-
使⽤相对于类路径的资源引⽤,例如:
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/> 使⽤完全限定资源定位符(URL),例如: <mapper url="file:///var/mappers/AuthorMapper.xml"/> 使⽤映射器接⼝实现类的完全限定类名,例如: <mapper class="org.mybatis.builder.AuthorMapper"/> 将包内的映射器接⼝实现全部注册为映射器,例如: <package name="org.mybatis.builder"/>
2. MyBatis映射配置文件常⽤配置解析
-
动态sql :
接口/** * 动态Sql <if> </if> and <where> </where> * @param user * @return * @throws IOException */ List<User> findByCondition(User user) throws IOException; /** * 多值查询 * 动态Sql <foreach></foreach> * @param ids * @return * @throws IOException */ List<User> findByIds(int[] ids) throws IOException;
SQL
<select id="findByCondition" parameterType="User" resultType="User"> select id,username from user <where> <if test="id != 0"> and id = #{id} </if> <if test="username != null"> and username = #{username} </if> </where> </select> <select id="findByIds" parameterType="list" resultType="User"> select id,username from user <where> <foreach collection="array" open="id in (" item="id" separator="," close=")"> #{id} </foreach> </where> </select>
测试
@Test public void userDaoFindByCondition() throws IOException { InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = build.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = new User(); user.setUsername("狗蛋"); // user.setId(3); List<User> byCondition = userDao.findByCondition(user); System.out.println("查询结果:"+byCondition); sqlSession.commit(); sqlSession.close(); } @Test public void userDaoFindByIds() throws IOException { InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = build.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); int[] ids = new int[]{1,2,3,4}; List<User> byCondition = userDao.findByIds(ids); System.out.println("查询结果:"+byCondition); sqlSession.commit(); sqlSession.close(); }
-
sql抽取
<sql id="selectUser"> select id,username from user </sql> <select id="findAll" resultType="User"> <include refid="selectUser"></include> </select> <select id="findByCondition" parameterType="User" resultType="User"> <include refid="selectUser"></include> <where> <if test="id != 0"> and id = #{id} </if> <if test="username != null"> and username = #{username} </if> </where> </select> <select id="findByIds" parameterType="list" resultType="User"> <include refid="selectUser"></include> <where> <foreach collection="array" open="id in (" item="id" separator="," close=")"> #{id} </foreach> </where> </select>
3.2 Mybatis的Dao层实现
3.2.1 传统开发方式
编写Dao层接口
编写Dao层实现
测试实现异常
3.2.2 代理开发方式
代理开发方式不需要编写Dao层实现, 仅编写接口即可, 由Mybatis动态代理生成实现提高开发效率
代理开发规范:
-
mapper.xml文件中的namespace值与mapper接口的全限定名相同
-
mapper.xml文件与mapper接口的statement的id相同
-
mapper.xml文件定义的parameterType类型与mapper接口输入的参数类型相同
-
mapper.xml文件定义的resultType类型与mapper接口输出的参数类型相同
Dao接口
package com.raphuscucullatus.dao;
import com.raphuscucullatus.pojo.User;
import java.io.IOException;
import java.util.List;
public interface UserDao {
List<User> findAll() throws IOException;
void findUser(User user) throws IOException;
void deleteUser(Integer id) throws IOException;
void updateUser(User user) 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">
<mapper namespace="com.raphuscucullatus.dao.UserDao">
<select id="findAll" resultType="com.raphuscucullatus.pojo.User">
select id,username from user
</select>
<select id="findUser" parameterType="com.raphuscucullatus.pojo.User">
insert into user values (${id},#{username},null,null)
</select>
<select id="updateUser" parameterType="com.raphuscucullatus.pojo.User">
update user set username=#{username} where id = #{id}
</select>
<select id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{id}
</select>
</mapper>
测试
@Test
public void testUserDao() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> all = userDao.findAll();
for (User user : all) {
System.out.println(user);
}
sqlSession.commit();
sqlSession.close();
}