目录

  • 一、JDBC操作数据库_问题分析
  • 二、自定义持久层框架_思路分析
  • 三、自定义框架_编码
    • 1、加载配置文件
    • 2、创建两个配置类对象
    • 3、解析配置文件,填充配置类对象
    • 4、创建SqlSessionFactory工厂接口及DefaultSqlSessionFactory实现类
    • 5、创建SqlSession会话接口及DefaultSqlSession实现类
    • 6、创建Executor执行器接口及SimpleExecutor实现类
    • 7、项目使用端利用自定义框架测试
  • 四、自定义框架_优化
    • 1、优化思路
    • 2、优化代码
    • 3、优化后测试
  • 五、Spring整合优化
  • 六、gitee代码

一、JDBC操作数据库_问题分析

JDBC使用流程

  1. 加载数据库驱动
  2. 创建数据库连接
  3. 创建编译对象
  4. 设置入参执行SQL
  5. 返回结果集

代码示例

public class JDBCTest {public static void main(String[] args) throws Exception {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 加载数据库驱动Class.forName("com.mysql.jdbc.Driver");// 通过驱动管理类获取数据库链接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis","root","123456789");// 定义sql语句?表示占位符String sql = "select * from user where name = ?";// 获取预处理statementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值preparedStatement.setString(1, "zhangsan");// 向数据库发出sql执行查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {int id = resultSet.getInt("id");String username = resultSet.getString("name");// 封装UserUser user = new User();user.setId(id);user.setName(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();}}}}
}

问题分析

存在问题 解决思路
数据库配置信息存在硬编码问题 使用配置文件
频繁创建、释放数据库连接问题 使用数据库连接池
SQL语句 使用配置文件
数据库配置信息存在硬编码问题 使用配置文件

二、自定义持久层框架_思路分析

主要分两部分,项目使用端:平常写代码所说的后台服务;持久层框架:即项目使用端引入的jar包

核心接口/类重点说明:

类名定义 角色定位 分工协作
Resources 资源辅助类 负责读取配置文件转化为输入流
Configuration 数据库资源类 负责存储数据库连接信息
MappedStatement SQL与结果集资源类 负责存储SQL映射定义、存储结果集映射定义
SqlSessionFactoryBuilder 会话工厂构建者 负责解析配置文件,创建会话工厂SqlSessionFactory
SqlSessionFactory 会话工厂 负责创建会话SqlSession
SqlSession 会话 指派执行器Executor
Executor 执行器 负责执行SQL (配合指定资源Mapped Statement)

项目使用端:

  1. 引入自定义持久层框架的jar包
  2. sqlMapConfig.xml:数据库配置信息,以及mapper.xml的全路径
  3. mapper.xml:SQL配置信息,存放SQL语句、参数类型、返回值类型相关信息

注意: sqlMapConfig.xml中引入mapper.xml是为了只读取一次配置文件,否则每个实体类会有一个mapper.xml,则需要读取很多次

自定义框架本身:

  1. 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中

  1. 创建两个javaBean(容器对象):存放配置文件解析出来的内容

  1. 解析配置文件(使用dom4j),并创建SqlSession会话对象

  1. 创建SqlSessionFactory接口以及实现类DefaultSqlSessionFactory

  1. 创建SqlSession接口以及实现类DefaultSqlSession

SqlSession接口定义以上方法,DefaultSqlSession来决定什么操作调用对应的sql执行器

  1. 创建Executor执行器接口以及实现类SimpleExecutor简单执行器

三、自定义框架_编码

项目使用端

创建sqlMapConfig核心配置文件:

<configuration><!--配置数据库信息--><dataSource><property name="driverClassname" value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://localhost:3306/mybatis" /><property name="username" value="root" /><property name="password" value="123456789" /></dataSource><!--引入映射配置文件,为了只加载一次xml--><mappers><mapper resource="mapper/UserMapper.xml"/></mappers></configuration>

创建映射配置文件:

获取某个sql语句的唯一标示statementId:namespace.id 如:user.selectList

<mapper namespace="user"><!--查询所有--><select id="selectList" resultType="com.xc.pojo.User" >select * from user</select><!--按条件查询--><select id="selectOne" resultType="com.xc.pojo.User" parameterType="com.xc.pojo.User" >select * from user where id = #{id} and name = #{name}</select></mapper>

User实体:

@Data
public class User {private Integer id;private String name;
}

pom.xml引入自定义框架

 <dependency><groupId>com.xc</groupId><artifactId>own-mybatis</artifactId><version>1.0-SNAPSHOT</version></dependency>

自定义框架本身

1、加载配置文件

  • 根据配置文件的路径,加载配置文件成字节输入流,存到内存中
public class Resources {public static InputStream getResourceAsStream(String path){return Resources.class.getClassLoader().getResourceAsStream(path);}
}

2、创建两个配置类对象

  • 映射配置类:mapper.xml解析出来内容
  • 每个pojo实体都会对应一个mapper.xml文件即一个MapperStatement对象
  • sqlCommandType:第四节 自定义框架_优化才会用到
@Data
public class MapperStatement {//唯一标识 statementId:namespace.idprivate String statementId;//返回值类型private String resultType;//参数类型private String parameterType;//sql语句private String sql;// 判断当前是什么操作的一个属性-增删改查private String sqlCommandType;
}
  • 核心配置类:数据库配置信息以及映射配置类的map集合
  • 将多个MapperStatement对象存入Map集合,statementId(namespace.id)作为key
  • 将所有的配置文件都聚合到Configuration中,方便一次读取以及统一管理
@Data
public class Configuration {//数据源对象private DataSource dataSource;//map.xml对象集合 key:statementIdprivate Map<String,MapperStatement> mapperStatementMap = new HashMap<>();
}

3、解析配置文件,填充配置类对象

  • XMLConfigBuilder类的parse方法:解析核心配置类,返回Configuration对象
  • 创建SqlSession工厂对象,以便之后创建SqlSession会话
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) throws Exception {//1.解析配置文件,封装容器对象:专门解析核心配置文件的解析类XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parse(inputStream);//2.创建SqlSessionFactory工厂对象return new DefaultSqlSessionFactory(configuration);}}
  • XMLConfigBuilder核心配置解析类里面嵌套着XMLMapperBuilder映射配置文件解析类
  • 输入流转化为Document对象,一是根据property标签获取数据库配置信息并创建数据源添加到configuration
  • 二是根据mapper标签通过XMLMapperBuilder解析类遍历解析配置文件同样添加到configuration的map集合类
public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}// 使用dom4j+xpath解析public Configuration parse(InputStream inputStream) throws Exception {//将xml转化为Document对象Document document = new SAXReader().read(inputStream);//获取跟节点,对于sqlMapConfig.xml来说就是<Configuration>标签Element rootElement = document.getRootElement();// -------------解析数据库配置文件----------------// "//"表示从匹配选择的当前节点,而不考虑它们的位置// 即这里获取数据源url用户密码信息// 例:<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>List<Element> propertyList = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : propertyList) {// 获取<property>标签中,name和value属性的值String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name,value);}// 创建数据源对象DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(properties.getProperty("driverClassName"));dataSource.setUrl(properties.getProperty("url"));dataSource.setUsername(properties.getProperty("username"));dataSource.setPassword(properties.getProperty("password"));// 将创建好的数据源添加到Configuration对象中configuration.setDataSource(dataSource);// -------------解析映射配置文件----------------/*1.获取映射配置文件路径2.根据路径进行映射文件的加载解析3.封装到MapperStatement,存入configuration的map集合中*/// 例:<mapper resource="mapper/UserMapper.xml"></mapper>List<Element> mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String resource = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(resource);// XMLMapperBuilder 专门解析映射配置文件的对象-最后会存入configuration的map集合对象中XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}
}
  • 与XMLConfigBuilder解析类原理一样
  • 传入configuration,并将解析好的MapperStatement对象添加到mapperStatementMap
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream resourceAsStream) throws Exception {// 将输入流转化为Document对象,并获取跟节点<mapper>Document document = new SAXReader().read(resourceAsStream);Element rootElement = document.getRootElement();// 例:<mapper namespace="user">String namespace = rootElement.attributeValue("namespace");/* 例:<select id="selectOne" resultType="com.xc.pojo.User" parameterType="com.xc.pojo.User" >select * from user where id = #{id} and name = #{name}</select>*/List<Element> selectList = rootElement.selectNodes("//select");for (Element element : selectList) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sql = element.getTextTrim();// 封装MapperStatement对象MapperStatement mapperStatement = new MapperStatement();String statementId = namespace + "." + id;mapperStatement.setStatementId(statementId);mapperStatement.setParameterType(parameterType);mapperStatement.setResultType(resultType);mapperStatement.setSql(sql);//第四节 自定义框架_优化才会用到mapperStatement.setSqlCommandType("select");// 添加到configurations的map集合中configuration.getMapperStatementMap().put(statementId,mapperStatement);}}
}

4、创建SqlSessionFactory工厂接口及DefaultSqlSessionFactory实现类

  • 为了创建SqlSession会话,调用增删改查方法
public interface SqlSessionFactory {// 创建SqlSession对象SqlSession openSession();}
  • 创建简单执行器,与核心配置类共同创建SqlSession会话实现类
  • configuration提供数据配置和sql以及参数和结果集封装
  • simpleExecutor提供JDBC执行sql底层原理
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {// 1.创建执行器对象-具体的包装jdbc的sql操作,关闭连接等Executor simpleExecutor = new SimpleExecutor();// 2.创建sqlSession对象-判断执行增删改查哪些操作等return new DefaultSqlSession(configuration,simpleExecutor);}
}

5、创建SqlSession会话接口及DefaultSqlSession实现类

  • statementId(“namespace.id”):定位具体Mapper.xml的sql语句以及入参和返回
  • param:替换sql语句中的占位符?,可能字符串、对象、Map、集合
public interface SqlSession {// 查询多个结果<E> List<E> selectList(String statementId, Object param) throws Exception;// 查询单个结果<T> T selectOne(String statementId, Object param) throws Exception;// 清理资源void close();}
  • 利用聚合进来的configuration对象获取MapperStatement映射配置对象向下传给执行器
  • 另外一个聚合进来的executor简单执行器来执行底层JDBC操作
  • DefaultSqlSession的作用则是聚合配置类分发到不同执行器的不同方法
  • 执行器种类:简单执行器、可重用执行器、批量执行器(这里只模拟第一种)
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic <E> List<E> selectList(String statementId, Object param) throws Exception {// 根据StatementId获取映射配置对象MapperStatementMapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);// 然后将具体的查询操作委派给SimpleExecutor执行器// 执行底层jdbc需要:1.数据库配置,2.sql配置信息return executor.query(configuration,mapperStatement,param);}@Overridepublic <T> T selectOne(String statementId, Object param) throws Exception {// 调用selectList()List<Object> selectList = selectList(statementId, param);if (selectList.size() == 1){return (T) selectList.get(0);}else if (selectList.size() > 1){throw new Exception("返回数据不止一条!!!");}else {return null;}}@Overridepublic void close() {executor.close();}}

6、创建Executor执行器接口及SimpleExecutor实现类

  • 执行器接口定义增删改查方法,具体的JDBC底层操作由它的实现类来完成
public interface Executor { <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object param) throws Exception;void close();
}
  • 执行器实现类整体流程就是JDBC那一套,从加载驱动到处理结果集
  • getBoundSql方法功能:
    • 一是将 <select>标签的sql语句“#{字段名}”替换成?赋值给finalSql。(这里使用的mybatis代码,不用深究就是根据正则表达式替换,最后有gitee代码链接)
    • 二是将替换?时候的字段名取出来放到ParameterMapping对象中,有多个,根据?次序,放入parameterMappingList集合
  • 入参根据<select>标签的parameterType属性获取全限定类名,反射获取Class对象
    1. 遍历parameterMappingList,获取字段名,加上Class对象获取Field属性类
    2. query查询方法有个param参数,即入参对象(有可能字符串,集合这里只考虑对象),通过Field属性和param对象通过反射获取属性值
  • 结果集根据<select>标签的resultType属性获取全限定类名,反射获取实例对象
    1. 通过结果集元数据获取字段名columnName,再resultSet.getObject获取数据库中对应字段值
    2. 通过java实体字段名和Class对象获取对应字段的Get方法的Method
    3. 这里说下1和2步骤中两个字段不是一回事,如果数据库和实体中不一样,这里需要转化成一致
public class SimpleExecutor implements Executor{private Connection connection = null;private PreparedStatement preparedStatement = null;private ResultSet resultSet = null;@Overridepublic <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object param) throws Exception {// 1.加载驱动,获取数据库连接connection = configuration.getDataSource().getConnection();// 2.获取preparedStatement预编译对象// 从mapperStatement中获取sql语句// 例:select * from user where id = #{id} and username = #{username}String sql = mapperStatement.getSql();// 1)需要转化为:select * from user where id = ? and username = ?// 2)需要将替换的值保存下来,后续?赋值需要用到BoundSql boundSql = getBoundSql(sql);String finalSql = boundSql.getFinalSql();preparedStatement = connection.prepareStatement(finalSql);// 3.根据入参赋值?// 获取入参的全限定类名 com.xc.pojo.UserString parameterType = mapperStatement.getParameterType();// 没有入参类型,表示sql没有参数,这里不需要?赋值if (parameterType != null){// 入参对象Class类Class<?> parameterTypeClass = Class.forName(parameterType);// 获取#{}参数字段listList<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {// 获取字段名称ParameterMapping parameterMapping = parameterMappingList.get(i);String paramName = parameterMapping.getContent();// 通过反射获取入参对象的Field属性Field field = parameterTypeClass.getDeclaredField(paramName);// 禁用安全检查,不用排除private,效率提升好多倍field.setAccessible(true);// param为User对象,通过属性反射获取到user对象id和name的属性值Object value = field.get(param);// 赋值占位符,占位符?数字从1开始preparedStatement.setObject(i + 1, value);}}// 4.执行sql,发起查询resultSet = preparedStatement.executeQuery();// 5.处理返回结果集List<E> list = new ArrayList<>();while (resultSet.next()){// 通过<select>标签的返回值类型,创建返回对象String resultType = mapperStatement.getResultType();Class<?> resultTypeClass = Class.forName(resultType);Object obj = resultTypeClass.newInstance();// 结果集的元数据信息:字段名,字段值等// resultSet: 一条结果集对应一张表的所有字段ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); i++) {//数据库字段名String columnName = metaData.getColumnName(i);// 字段的值Object value = resultSet.getObject(columnName);// columnName:数据库字段,而下方需要实体中的字段,如果两边不一样,则这需要有一个转化// 获取读写方法即get、set方法PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);// 获取读方法-字段的set方法Method writeMethod = propertyDescriptor.getWriteMethod();// 反射为返回对象赋值// 参数1:实例对象 参数2:要设置的值writeMethod.invoke(obj,value);}list.add((E) obj);}return list;}/*** @Description 获取?占位符的sql,以及保存#{}中的字段名称** 1、将<select>标签中的sql的 "#{字段名}" 整个部分替换成?,赋值 finalSql* 2、截取#{}中的字段名称,添加到ParameterMapping对象的content属性,多个,赋值List<ParameterMapping>** @Author xuchang* @Date 2022/10/19 16:44:51*/private BoundSql getBoundSql(String sql) {// 1.创建标记处理器:配合标记解析器完成标记的处理解析工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 2.创建标记解析器GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);// #{}占位符替换成? 2.解析替换的过程中 将#{}里面的值保存下来 ParameterMappingString finalSql = genericTokenParser.parse(sql);// #{}里面的值的一个集合 id usernameList<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();return new BoundSql(finalSql, parameterMappings);}@Overridepublic void close() {// 释放资源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();}}}
}
@Data
@AllArgsConstructor
public class BoundSql {// 用?做占位符的sql语句private String finalSql;// 字段名称的集合private List<ParameterMapping> parameterMappingList;
}
@Data
@AllArgsConstructor
public class ParameterMapping {// 保存#{}中对于的字段名称private String content;
}

7、项目使用端利用自定义框架测试

  • 优点:完成一张表的增删改查只需要创建一个实体类。(其他类接口都是必须要有的)
  • 缺点1:sqlSession会话对象创建频繁,每次需要User的CRUD,就需要创建
  • 缺点2:调用方法需要手动添加参数statementId:namespace.id
@Test
public void test1() throws Exception {// 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2.解析配置文件,封装到Configuration对象,创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 3.生产sqlSession 创建执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4.调用sqlSession方法User user = new User();user.setId(100);user.setName("zhangsan");User userOne = sqlSession.selectOne("user.selectOne", user);System.out.println("查询单个用户:"+userOne);List<User> userList = sqlSession.selectList("user.selectList",null);System.out.println("查询所有用户:"+userList.toString());// 5.释放资源sqlSession.close();
}

输出结果:

查询单个用户:User(id=100, name=zhangsan)
查询所有用户:[User(id=100, name=zhangsan), User(id=120, name=Lisi)]

四、自定义框架_优化

1、优化思路

  • 去掉statementId硬编码:创建一个UserMapper或IUserDao接口,每个方法名、入参、返回值和mapper.xml的<select>标签的id、parameterType、resultType一一对应
  • sqlSession创建频繁:通过动态代理创建IUserDao的实现类,内容则是sqlSession.selectOne和sqlSession.selectList;这样sqlSession只需要创建一次,以后每次需要User的CRUD,则调用代理对象对应方法即可

2、优化代码

在sqlSession中添加方法

public interface SqlSession {...// 生成代理对象<T> T getMapper(Class<?> mapperClass);}

对应实现类方法

  • 动态代理对象:不需要编译,运行期间生成字节码,通过传入的类加载器加载,只存在于内存中
  • mapperClass:UserMapper或IUserDao接口的Class对象
  • 代理类调用接口中的方法,则会被拦截进入invoke方法,因为没有目标类,则具体的实现都在invoke里面了
  • 通过mapperClass和方法名获取到statementId
  • statementId和参数有了,但是DefaultSqlSession要有CRUD查询和更新操作,所以sqlCommandType来区分
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic <E> List<E> selectList(String statementId, Object param) throws Exception {// 根据StatementId获取映射配置对象MapperStatementMapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);// 然后将具体的查询操作委派给SimpleExecutor执行器// 执行底层jdbc需要:1.数据库配置,2.sql配置信息return executor.query(configuration,mapperStatement,param);}@Overridepublic <T> T selectOne(String statementId, Object param) throws Exception {// 调用selectList()List<Object> selectList = selectList(statementId, param);if (selectList.size() == 1){return (T) selectList.get(0);}else if (selectList.size() > 1){throw new Exception("返回数据不止一条!!!");}else {return null;}}@Overridepublic void close() {executor.close();}@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK动态代理生成基于接口的对象// 1、创建一个类(代理类),实现目标接口,实现所有的方法实现// 2、动态代理类:代码运行期间生成的,而非编译期Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {// proxy:生成的代理对象本身,很少用// method:调用接口中哪个方法,则执行对应代理里的对应方法// args:调用方法的参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 执行底层JDBC// 1.获取statementId// ps:约定接口中的方法名要与<select>标签的id属性名一致,// 这样就可以通过接口获取statementId = namespace.idString methodName = method.getName();// 方法名 - findAllString className = mapperClass.getName();//接口类名 - com.xc.do.IUserDaoString statementId = className + "." + methodName;// 2.判断调用sqlSession中CRUD的什么方法// MapperStatement类添加属性sqlCommandType-sql增删改查类型MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);// select  update delete insertString sqlCommandType = mapperStatement.getSqlCommandType();switch (sqlCommandType){case "select" :// 3.判断调用selectOne还是selectList// 获取返回值类型,Type genericReturnType = method.getGenericReturnType();// ParameterizedType是Type的子接口,表示一个有参数的类型,例如Collection<T>,Map<K,V>等if(genericReturnType instanceof ParameterizedType){if(args != null) {return selectList(statementId, args[0]);}return  selectList(statementId, null);}return selectOne(statementId,args[0]);case "update":// 执行更新方法调用break;case "delete":// 执行delete方法调用break;case "insert":// 执行insert方法调用break;}return null;}});return (T) proxyInstance;}
}

3、优化后测试

  • 缺点:相较于优化前,需要新添加一个接口
  • 优点:不需要手动添加statementId,SqlSession不用频繁创建
@Test
public void test2() throws Exception {// 1.根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream resourceAsSteam = Resources.getResourceAsStream("sqlMapConfig.xml");// 2.解析了配置文件,封装了Configuration对象  2.创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);// 3.生产sqlSession 创建了执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4.调用sqlSession方法IUserDao userDao = sqlSession.getMapper(IUserDao.class);List<User> all = userDao.findAll();for (User user : all) {System.out.println(user);}// 5.释放资源sqlSession.close();
}

五、Spring整合优化

  • 与Spring整合之后,InputStream、SqlSessionFactory、SqlSession、IUserDao代理对象统统不需要自己创建
  • 全都交给了spring容器管理,我们要做的就是@Autowired IUserDao userDao
  • 然后就可以用代理对象调用增删改查方法了

六、gitee代码

  • 手写源码框架项目: https://gitee.com/xuchang614/own-mybatis

  • 测试框架项目: https://gitee.com/xuchang614/own-mybatis-test

手写Mybatis源码(原来真的很简单!!!)相关推荐

  1. Node进阶——之事无巨细手写Koa源码

    作者 rocYoung Koa是一个基于Node.js的Web开发框架,特点是小而精,对比大而全的Express(编者按:此处是相对来说,国内当然是有Egg.js和ThinkJS),两者虽然由同一团队 ...

  2. Node进阶—事无巨细手写Koa源码

    作者 rocYoung Koa是一个基于Node.js的Web开发框架,特点是小而精,对比大而全的Express(编者按:此处是相对来说,国内当然是有Egg.js和ThinkJS),两者虽然由同一团队 ...

  3. node进阶——之事无巨细手写koa源码(转)

    https://juejin.im/post/5ba48fc4e51d450e704277fa koa是一个基于nodejs的web开发框架,特点是小而精,对比大而全的express,两者虽然由同一团 ...

  4. java 手写签名,signature java html5+ 手写签名 源码 Develop 238万源代码下载- www.pudn.com...

    文件名称: signature下载 收藏√  [ 5  4  3  2  1 ] 开发工具: Java 文件大小: 491 KB 上传时间: 2013-08-03 下载次数: 17 提 供 者: 孙晨 ...

  5. 【手写 Promise 源码】第八篇 - 完善 Promise 并通过 promise-aplus-tests 测试

    一,前言 上一篇,实现 Promise 对返回值 x 各种情况的分析和处理,主要涉及以下几个点: 回顾了相关的 Promise A+ 规范内容: 根据 Promise A+ 规范描述和要求,实现了核心 ...

  6. 跨年巨作 13万字 腾讯高工手写JDK源码笔记 带你飙向实战

    灵魂一问,我们为什么要学习JDK源码? 当然不是为了装,毕竟谁没事找事虐自己 ... 1.面试跑不掉.现在只要面试Java相关的岗位,肯定或多或少会会涉及JDK源码相关的问题. 2.弄懂原理才不慌.我 ...

  7. 【Java进阶营】膜拜 13万字 腾讯高工手写JDK源码笔记带你飙向实战

    灵魂一问,我们为什么要学习JDK源码? 当然不是为了装,毕竟谁没事找事虐自己 - 1.面试跑不掉.现在只要面试Java相关的岗位,肯定或多或少会会涉及JDK源码相关的问题. 2.弄懂原理才不慌.我们作 ...

  8. 【手撕MyBatis源码】MyBatis映射体系

    文章目录 映射工具MetaObject 基本功能 底层结构 获取属性值的流程 ResultMap结果集映射 手动映射 自动映射 嵌套子查询 循环依赖 懒加载 原理 内部结构 Bean代理过程 联合查询 ...

  9. react学习笔记 react-router-dom react-redux基础使用及手写基础源码 组件反射 react原理

    vdom diff 高效的diff算法 新老vdom树比较 更新只需要更新节点 数据变化检测 batch dom读写 组件多重继承 //parent components export default ...

最新文章

  1. poj1286(纯Polya定理)
  2. MySQL 时间类型 DATE、DATETIME和TIMESTAMP
  3. “error LNK1169: 找到一个或多个多重定义的符号”的解决方法
  4. 微信小程序,图片居中显示,适配不同机型
  5. java:1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数。
  6. LeetCode 811. 子域名访问计数
  7. python实现多人聊天论文_python网络编程 双人多人聊天
  8. Flask 验证模式
  9. java search 不能使用方法_ElasticSearch实战系列三: ElasticSearch的JAVA API使用教程
  10. 老公年收入百万,却不愿拿出二十万帮我弟弟买房子,我该离婚吗?
  11. 普通人买得到国债吗?
  12. eclipse技巧总结
  13. linux 创建目录和删除目录
  14. Linux下Shell脚本命令行参数:getopt、getopts用法
  15. 自定义事件和jQuery插件
  16. Android技术专家 高焕堂 推荐这本书
  17. Veu表达与v-model
  18. Cesium中如何获取鼠标单击位置的经纬度
  19. MySQL(六)事物(ADID,四种隔离级别)(七)索引(索引测试,原则)
  20. nyoj 125 盗梦空间

热门文章

  1. 消费与储蓄的决定-中国视角下的宏观经济
  2. 跑moses摩西翻译系统
  3. 用jQuery Masonry快速构建一个pinterest网站布局
  4. STM32F103学习笔记(1)——stlink和miniusb下载两种方式
  5. 网购六大注意事项,你知道几条?
  6. 图形学 实验四 梁barsky算法
  7. 似乎一夜间,所有的枯枝都绽出了绿芽
  8. am5728 使用mailbox 通信
  9. 经度、纬度、高度转换成大地坐标系XYZ的程序
  10. 设某一个班共有N(N=20)名学生,期末考试5门课程,请编一程序评定学生的奖学金,要求打印输出一、二等奖学金学生的学号、姓名和各门课成绩。