1. 主要流程
-
使用端:
项目: 引入自定义持久层框架的Jar包
提供两部分配置信息数据库配置信息、sql配置信息:SQL语句、参数类型、返回值类型
使用配置文件来提供这两份不配置信息:
(1)sqlMapConfig.xml:存放数据库配置信息,存放mapper.xml的全路径
(2)mapper.xml:存放sql配置信息 -
自定义持久层框架本身:
工程:本质就是对JDBC代码进行了封装
(1)加载配置文件:根据文件的路径,加载配置文件成字节输入流,存储在内存中,创建Resource类 方法: InputSteam getResourceAsSteam(String path)
(2)创建两个javaBean:(容器对象)存放的就是对配置文件解析出来的内容
configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容
MappedStatement:映射配置类:存放mapper.xml解析出来的内容
(3)解析配置文件:dom4j
创建类:SqlSessionFactoryBuilder 方法build(InputSteam in)
第一:使用dom4j解析配置文件,将解析出来的内容封装在对象中
第二:创建SqlSessionFacroty对象;生产salSession:对话对象(工厂模式)
(4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
第一:openSession():生产sqlSession
(5)创建SqlSession接口及实现类DefaultSession
定义对数据库的crud操作:selectList() selectOne() update()
delete()
(6)创建Executor接口及实现类SimpleExecutor实现类
query(Configuration, MappedStatement, Object… params):执行的就是JDBC代码。 -
常用pom.xml配置
<properties>
<project.build.sourceEnding>UTF-8</project.build.sourceEnding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
- 数据库配置信息
<dataSource>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf8&serverTimezone=UTC&useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
- 添加依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. 项目实现
- jar包结构
使用端:
- 主线
//主线
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
//第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
XMLCongfigBuilder xmlCongfigBuilder = new XMLCongfigBuilder();
Configuration configuration = xmlCongfigBuilder.paseConfig(in); //解析配置文件
//第二步:创建sqlSessionFactory对象(工厂类):生产sqlSession:会话对象 增删改查
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
- pojo配置
主要配置文件:
public class Configuration {
private DataSource dataSource; //连接数据库(连接池)
//key: statementid 存储的是sql语句
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;
}
}
配置sql文件
public class MappedStatement {
//id标识
private String id;
//返回值类型(全路径)
private String resultType;
//参数类型
private String paramterType;
//sql语句
private String sql;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getParamterType() {
return paramterType;
}
public void setParamterType(String paramterType) {
this.paramterType = paramterType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
- 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public class Resources {
// 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public static InputStream getResourceAsSteam(String path) {
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
2.1 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
- 加载文件成字节输入流
public class Resources {
// 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public static InputStream getResourceAsSteam(String path) {
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
public class XMLCongfigBuilder {
private Configuration configuration;
public XMLCongfigBuilder(){
this.configuration = new Configuration();
}
//封装一层,解析文件(解析连接数据库的主要信息,并解析sql语句所在的路径).
public Configuration paseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream); //字节输入流,解析成树结构的document
//<configuration>
Element rootElement = document.getRootElement(); //获取xml根路径
List<Element> list = rootElement.selectNodes("//property"); //获取property这个属性的所有值
Properties properties = new Properties();
for (Element element : list) {
String name = element.attributeValue("name"); //获取标签内的属性
String value = element.attributeValue("value"); //同上
properties.setProperty(name,value);
}
//创建连接池:解决频繁创建数据库连接问题
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(comboPooledDataSource); //连接池放进配置文件中
//mapper.xml解析:拿到路径--》 字节输入流--》dom4j进行解析
List<Element> mapperList = rootElement.selectNodes("//mapper"); //获取mapper这个属性,对于具体的配置sql语句进行解析
for (Element element : mapperList) {
//从同一个配置文件中拿到另一个配置文件路径,然后进行解析
String mapperPath = element.attributeValue("resource"); //获取路径resource这个标签内的值
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
//dom4j 进行解析 SQL语句跟本类一样需要解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); //将
xmlMapperBuilder.parse(resourceAsSteam);
}
return configuration;
}
}
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration; //没有创建新对象,还是在之前的引用
}
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream); //字节
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace"); //获取标签内的属性值使用attributeValue
List<Element> list = rootElement.selectNodes("//select"); //获取所有select的子标签,这个是Xpath方式
for (Element element : list) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterType = element.attributeValue("paramterType");
String sqlText = element.getTextTrim(); //获取标签中的值
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
String key = namespace + "." + id;
configuration.getMappedStatementMap().put(key, mappedStatement);
}
}
}
2.2 第二步:创建sqlSessionFactory对象(工厂类):生产sqlSession:会话对象 增删改查
- 创建会话工厂接口
public interface SqlSessionFactory {
public SqlSession openSession();
}
- 实现会话工厂接口:调用会话openSession回返回一个会话对象
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration){
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
//返回一个绘画对象
return new DefaultSqlSession(configuration);
}
}
- 会话对象接口:这里面定义有哪些操作(CRUD)
public interface SqlSession {
public <E> List<E> selectList(String statementid, Object... params) throws Exception;
public <T> T selectOne(String statementid, Object... params) throws Exception;
}
- 实现会话接口:
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
//将要去完成SimpleExecutor
SimpleExecutor simpleExecutor =new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
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 class IpersistenceTest {
@Test
public void test() throws Exception {
//传入配置文件
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
//会话工厂建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
//工厂对象返回一个会话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//存在硬编码问题,还需要进一步封装
User user = new User();
user.setId(1);
user.setUsername("张三");
// User user2 = sqlSession.selectOne("user.selectOne", user);
// System.out.println(user2);
//调用会话中的方法(CRUD)
List<User> users = sqlSession.selectList("user.selectList");
for(User user1 : users){
System.out.println(user1);
}
}
}
3. 在进行封装
定义接口:
public interface IUserDao {
//根据条件查询用户
public User findByCondition(User user) throws Exception;
//查询所有用户
public List<User> findAll() throws Exception;
}
接口实现:
public class UserDaoImpl implements IUserDao{
@Override
public User findByCondition(User user) throws Exception {
//传入配置文件
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
//会话工厂建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
//工厂对象返回一个会话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//存在硬编码问题,还需要进一步封装
User user2 = sqlSession.selectOne("user.selectOne", user);
return user2;
}
@Override
public List<User> findAll() throws Exception {
//传入配置文件
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
//会话工厂建立
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
//工厂对象返回一个会话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//调用会话中的方法(CRUD)
List<User> users = sqlSession.selectList("user.selectList");
return users;
}
}
3. 自定义持久层框架问题分析
- 自定义持久层框架,存在代码重复,整个操作的过程模板重复(加载配置文件、创建sqlSessionFactory、 生产sqlSession)
- statementId存在硬编码问题
解决思路: 使用代理模式生成Dao层接口的代理接口的代理实现类
注:代理对象调用接口中的任意方法,都会执行invoke方法
接口
public interface IUserDao {
//根据条件查询用户
public User findByCondition(User user) throws Exception;
//查询所有用户
public List<User> findAll() throws Exception;
}
代理接口:
public interface SqlSession {
public <E> List<E> selectList(String statementid, Object... params) throws Exception;
public <T> T selectOne(String statementid, Object... params) throws Exception;
//为Dao接口生成代理实现类
public <T> T getMapper(Class<?> mapperClass);
}
代理接口实现
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
//将要去完成SimpleExecutor
SimpleExecutor simpleExecutor =new SimpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
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("查询结果为空或者返回结果过多!");
}
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
// 方法名:findAll findByCondition
String methodName = method.getName();
String className = method.getDeclaringClass().getName(); //该类的全限定名
String statementId = className+"."+methodName;
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T) proxyInstance;
}
}
注意配置文件中的namespace要写接口的全限定名,select中的id要写接口中的方法名
<mapper namespace="com.lagou.dao.IUserDao">
<!-- sql唯一标识:namespace.id来组成: statementid-->
<select id="findAll" resultType="com.lagou.pojo.User">
select * from user
</select>
<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>