前言

本文适合有一定java基础的同学,通过自定义持久层框架,可以更加清楚常用的mybatis等开源框架的原理。


JDBC操作回顾及问题分析

学习java的同学一定避免不了接触过jdbc,让我们来回顾下初学时期接触的jdbc操作吧

以下代码连接数据库查询用户表信息,用户表字段分别为用户id,用户名username。

Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
User user = new User();
try {
// 加载数据库驱动
//Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mimashi3124");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
preparedStatement.setString(1, "盖伦");
// 向数据库发出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操作数据库存在以下问题:

  1. 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。
  2. Sql语句我们是写在代码里的,代码不容易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
  3. 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便

问题解决思路

  1. 使⽤数据库连接池初始化连接资源,避免资源浪费
  2. 将sql语句抽取到xml配置中,这种sql的变动只用关注xml文件,不比去一堆java代码里改写sql
  3. 参数硬编码问题可以使用反射、内省等技术、自动将实体与表字段进行映射。

自己动手写个持久层框架

接下来,我们来一个个解决上面的问题

数据库连接池我们可以直接使用c3p0提供的ComboPooledDataSource即可

为了解决sql硬编码问题,我们要把sql写到xml文件中,那自然是要定义一个xml文件了。

光有sql肯定不行,毕竟我们要先连接数据库,sql语句才有存在的意义。所以xml中得先定义数据配置信息,然后才是sql语句。

1.定义配置xml文件

我们新建一个sqlMapConfig.xml,定义数据源信息、并且增加两个sql语句,parameterType为sql执行参数,resultType为方法返回实体。

代码如下(数据库不同版本使用驱动类可能不同):

<configuration>
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>-->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mimashi3124"/> <select id="selectOne" parameterType="org.example.pojo.User"
resultType="org.example.pojo.User">
select * from user where id = #{id} and username =#{username}
</select> <select id="selectList" resultType="org.example.pojo.User">
select * from user
</select>
</configuration>

现在xml文件数据库信息也有了,sql语句定义也有了,还有什么问题呢?

我们实际中对sql的操作会涉及到不同的表,所以我们改进一下,把每个表的sql语句单独放在一个xml里,这样结构更清晰就容易维护。

优化以后的xml配置现在是这样了

sqlMapConfig.xml

<configuration>
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>-->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mimashi3124"/>
<!--引⼊sql配置信息-->
<mapper resource="mapper.xml"></mapper>
</configuration>

mapper.xml

<mapper namespace="user">
<select id="selectOne" parameterType="org.example.pojo.User"
resultType="org.example.pojo.User">
select * from user where id = #{id} and username =#{username}
</select> <select id="selectList" resultType="org.example.pojo.User">
select * from user
</select>
</mapper>

顺便定义一下业务实体User

public class User {
private int 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 + '\'' +
'}';
}
}

2.读取配置文件

读取完成以后以流的形式存在,不好操作,所以我们要做解析拿到信息,创建实体对象来存储。

  1. Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id
  2. MappedStatement:存放sql语句、输⼊参数类型、输出参数类型

xml解析我们使用dom4j

首先引入maven依赖

代码如下(mysql驱动版本根据实际使用mysql版本调整):

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties> <dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>

数据库配置实体 Configuration

public class Configuration {
//数据源
private DataSource dataSource;
//map集合: key:statementId value:MappedStatement
private Map<String,MappedStatement> mappedStatementMap = new HashMap<>(); public DataSource getDataSource() {
return dataSource;
} public Configuration setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
} public Map<String, MappedStatement> getMappedStatementMap() {
return mappedStatementMap;
} public Configuration setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
return this;
}
}

Sql语句信息实体

public class MappedStatement {

    //id
private String id;
//sql语句
private String sql;
//输⼊参数
private String parameterType;
//输出参数
private String resultType; public String getId() {
return id;
} public MappedStatement setId(String id) {
this.id = id;
return this;
} public String getSql() {
return sql;
} public MappedStatement setSql(String sql) {
this.sql = sql;
return this;
} public String getParameterType() {
return parameterType;
} public MappedStatement setParameterType(String parameterType) {
this.parameterType = parameterType;
return this;
} public String getResultType() {
return resultType;
} public MappedStatement setResultType(String resultType) {
this.resultType = resultType;
return this;
}
}

顺便定义一个Resources类来读取xml文件流

public class Resources {
public static InputStream getResourceAsSteam(String path) {
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}

接下来就是实际的解析了,因为解析代码比较多,我们考虑封装类单独处理解析

定义XMLConfigBuilder类解析数据库配置信息

public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder() {
this.configuration = new Configuration();
} public Configuration parserConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
List<Element> propertyElements = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element propertyElement : propertyElements) {
String name = propertyElement.attributeValue("name");
String value = propertyElement.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); //将configuration传入XMLMapperBuilder中做sql语句解析。
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(this.configuration);
List<Element> mapperElements = rootElement.selectNodes("//mapper");
for (Element mapperElement : mapperElements) {
String mapperPath = mapperElement.attributeValue("resource");
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mapperPath);
xmlMapperBuilder.parse(resourceAsStream);
}
return configuration;
}
}

定义XMLMapperBuilder类解析数据库配置信息

public class XMLMapperBuilder {

    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public void parse(InputStream inputStream) throws DocumentException,
ClassNotFoundException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
List<Element> select = rootElement.selectNodes("select");
for (Element element : select) { //id的值
String id = element.attributeValue("id");
String parameterType = element.attributeValue("parameterType"); //输⼊参数
String resultType = element.attributeValue("resultType"); //返回参数
//statementId,后续调用通过statementId,找到对应的sql执行
String key = namespace + "." + id;
//sql语句
String textTrim = element.getTextTrim();
//封装 mappedStatement
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParameterType(parameterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(textTrim);
//填充 configuration
configuration.getMappedStatementMap().put(key, mappedStatement);
}
}
}

现在我们可以通过调用配置解析的方法拿到Configuration对象了。但是我们实际使用,肯定是希望我给你配置信息、sql语句,再调用你的方法就返回结果了。

所以我们还需要定义一个数据库操作接口(类)

3.定义sql操作接口SqlSession

public interface SqlSession {
//查询多个
public <E> List<E> selectList(String statementId, Object... param) throws Exception; //查询一个
public <T> T selectOne(String statementId,Object... params) throws Exception;
}

对操作接口SqlSession做具体实现,这里主要是通过statementId找到对应的sql信息,进行执行

代码中simpleExcutor做真正的数据库语句执行、返回参数封装等操作

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
private Executor simpleExcutor = new SimpleExecutor(); public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
} @Override
public <E> List<E> selectList(String statementId, Object... param) throws Exception {
MappedStatement mappedStatement =
configuration.getMappedStatementMap().get(statementId);
List<E> query = simpleExcutor.query(configuration, mappedStatement, param);
return query;
} @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("返回结果过多");
}
}
}

4.编写数据库执行逻辑

数据库操作类DefaultSqlSession中的selectList方法调用到了simpleExcutor.query()方法

public class SimpleExecutor implements Executor {

    private Connection connection = null;

    @Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
//获取连接
connection = configuration.getDataSource().getConnection();
// select * from user where id = #{id} and username = #{username} String sql =
String sql = mappedStatement.getSql();
//对sql进⾏处理
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); // 4. 设置参数
//获取到了参数的全路径
String parameterType = mappedStatement.getParameterType();
Class<?> parameterTypeClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent(); //反射
Field declaredField = parameterTypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); }
// 5. 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType); ArrayList<Object> objects = new ArrayList<>(); // 6. 封装返回结果集
while (resultSet.next()){
Object o =resultTypeClass.newInstance();
//元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名
String columnName = metaData.getColumnName(i);
// 字段的值
Object value = resultSet.getObject(columnName);
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
objects.add(o);
}
return (List<E>) objects;
} @Override
public void close() throws SQLException { } private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
if(parameterType!=null){
Class<?> aClass = Class.forName(parameterType);
return aClass;
}
return null; } private BoundSql getBoundSql(String sql) {
//标记处理类:主要是配合通⽤标记解析器GenericTokenParser类完成对配置⽂件等的解 析⼯作,其中
//TokenHandler主要完成处理
ParameterMappingTokenHandler parameterMappingTokenHandler = new
ParameterMappingTokenHandler();
//GenericTokenParser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根 据给定的
// 标记处理器(TokenHandler)来进⾏表达式的处理
//三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处 理器)
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}",
parameterMappingTokenHandler);
String parse = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappings =
parameterMappingTokenHandler.getParameterMappings();
return new BoundSql(parse, parameterMappings);
}
}

上面的注释比较详细,流程为

  1. 根据对应的statementId获取到要执行的sql语句、调用参数、返回参数。
  2. 对sql的占位符进行解析、调用参数进行设置
  3. 根据解析到的入参字段,通过反射获取到对应的值,进行sql语句参数设定
  4. 执行sql语句,使用反射、内省,根据数据库表和实体的对应关系,完成对象属性的设置,最终返回结果。

通过以上步骤,我们获取到了数据库配置、sql语句信息。定义了数据库操作类SqlSession,但是我们并没有在什么地方调用解析配置文件。

我们还需要一个东西把两者给串起来,这里我们可以使用工厂模式来生成SqlSession

使用工厂模式创建SqlSession

public interface SqlSessionFactory {
public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
} @Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}

同时为了屏蔽构建SqlSessionFactory工厂类时获取Configuration的解析过程,我们可以使用构建者模式来获得一个SqlSessionFactory类。

public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException, ClassNotFoundException {
XMLConfigBuilder xmlConfigerBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigerBuilder.parserConfiguration(inputStream);
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}

5.调用测试

终于好了,通过以上几个步骤我们现在可以具体调用执行代码了。

 public static void main(String[] args) 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);
List<User> users = sqlSession.selectList("user.selectList");
for (User user1 : users) {
System.out.println(user1);
}
}

代码正确执行,输出

⾃定义框架优化

上述⾃定义框架,解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编

码,⼿动封装返回结果集等问题,现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题呢?

问题如下:

  1. dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭sqlsession)
  2. dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码

我们可以使用代理模式,生成代理对象,在调用之前获取到执行方法的方法名、具体类。这样我们就能获取到statementId。

为SqlSession类新增getMappper方法,获取代理对象

public interface SqlSession {
public <E> List<E> selectList(String statementId, Object... param) 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;
private Executor simpleExcutor = new SimpleExecutor(); public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
} @Override
public <E> List<E> selectList(String statementId, Object... param) throws Exception {
MappedStatement mappedStatement =
configuration.getMappedStatementMap().get(statementId);
List<E> query = simpleExcutor.query(configuration, mappedStatement, param);
return query;
} @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) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// selectOne
String methodName = method.getName();
// className:namespace
String className = method.getDeclaringClass().getName();
//statementId
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) proxyInstance;
}
}

定义业务数据dao接口

public interface IUserDao {

    //查询所有用户
public List<User> findAll() throws Exception; //根据条件进行用户查询
public User findByCondition(User user) throws Exception;
}

接下来我们只需获取到代理对象,调用方法即可。

public class Main2 {
public static void main(String[] args) throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取到代理对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> all = userDao.findAll();
for (User user1 : all) {
System.out.println(user1);
}
}
}

总结

为了解决JDBC操作数据库存在的问题,我们主要干了这些事

  1. 定义sql配置文件来存放数据源、sql语句等信息。
  2. 解析xml文件,存放到类对象Configuration、MappedStatement中。
  3. 通过反射、自省等技术完成了参数的动态设置、返回结果集的封装
  4. 为了解决statementId硬编码问题,我们使用了代理模式创建代理类来获取statementId信息。
  5. 在过程中使用到了构建者模式、工厂模式。

以下为本文的大体流程图:

源码以及数据库建表语句链接 :java自定义简易持久层框架

如果本文对你有帮助,请右下角点下哈

Java精进-手写持久层框架的更多相关文章

  1. 闭关修炼180天--手写持久层框架(mybatis简易版)

    闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...

  2. Mybatis(一):手写一套持久层框架

    作者 : 潘潘 未来半年,有幸与导师们一起学习交流,趁这个机会,把所学所感记录下来. 「封面图」 自毕业以后,自己先创业后上班,浮沉了近8年,内心着实焦躁,虽一直是走科班路线,但在技术道路上却始终没静 ...

  3. Java数据持久层框架 MyBatis之背景知识三

    摘录自:http://www.cnblogs.com/lcngu/p/5437281.html 对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.or ...

  4. Java持久层框架Mybatis入门

    MyBatis是什么 MyBatis是Java的持久层框架,GitHub的star数高达15.8k,是Java技术栈中最热门的ORM框架之一.它支持自定义SQL.存储过程以及高级映射,可以通过XML或 ...

  5. Java数据持久层框架 MyBatis之API学习一(简介)

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  6. Java数据持久层框架 MyBatis之背景知识二

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  7. Java数据持久层框架 MyBatis之背景知识一

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  8. Java工程师高薪训练营-第一阶段 开源框架源码解析-模块一 持久层框架涉及实现及MyBatis源码分析-任务一:自定义持久层框架

    目录 任务一:自定义持久层框架 1.1 JDBC回顾及问题分析 1.2 自定义持久层框架思路分析 1.3 IPersistence_Test编写 1.3.1 XXXMapper.xml详解 1.3.2 ...

  9. 【笔记】拉勾Java工程师高薪训练营-第一阶段 开源框架源码解析-模块一 持久层框架涉及实现及MyBatis源码分析-任务一:自定义持久层框架

    以下笔记是我看完视频之后总结整理的,部分较为基础的知识点也做了补充,如有问题欢迎沟通. 目录 任务一:自定义持久层框架 1.1 JDBC回顾及问题分析 1.2 自定义持久层框架思路分析 1.3 IPe ...

随机推荐

  1. [OCWA 模拟赛ADay1] 钢铁侠的逃离

    Description 给定 \(A,B,N\) ,求 \(\sum\limits_{i=1}^{N} popcount(B+i*A)\) ,其中 \(popcount\) 是指数 \(x\) 在二进 ...

  2. 历经70+场面试,我发现了大厂面试的bug,并总结其中心得

    想起了学弟在去年秋招时面试了50余家,加上暑期实习面试了20余家,加起来也面试了70余场. 基本把国内有名的互联网公司都面了一遍,不敢说自己的面试经验很丰富,但也是不差的. 这次专门把大厂的面试做了个 ...

  3. 创建NuGet本地包源

    NuGet 是免费.开源的包管理开发工具,专注于在 .NET 应用开发过程中,简单地合并第三方的组件库.使用Visual Studio 可以很方便地将类库等项目打包发布,最简单的办法是上传到Nuget ...

  4. django框架8

    内容概要 ajax简介 前后端传输数据编码格式 ajax发送json格式数据 ajax携带文件数据 回调机制处理策略 内容详情 ajax简介 页面不刷新的情况下可以与后端进行数据交互 异步提交 局部刷 ...

  5. Cent OS8.0 及以上版本安装禅道教程

    Cent OS8系统下安装禅道需要搭建环境如下:httpd ,mariadb , php7.2 再运行禅道 一,环境说明: 运行环境推荐使用 Apache + PHP(7.0/7.1/7.2版本) + ...

  6. FlinkSQL 之乱序问题

    乱序问题 在业务编写 FlinkSQL 时, 非常常见的就是乱序相关问题, 在出现问题时,非常难以排查,且无法稳定复现,这样无论是业务方,还是平台方,都处于一种非常尴尬的地步. 在实时 join 中, ...

  7. 关于vue cli 使用iview 自定义主题遇到的坑

    定制主题,这里讲变量覆盖 当你老老实实的把上面文档中的代码一一复制粘贴到项目文件中时,发现了还没装less,所以你就 npm install less –savenpm install less-lo ...

  8. 集成学习——GBDT(手推公式)

  9. Spring框架系列(7) - Spring IOC实现原理详解之IOC初始化流程

    上文,我们看了IOC设计要点和设计结构:紧接着这篇,我们可以看下源码的实现了:Spring如何实现将资源配置(以xml配置为例)通过加载,解析,生成BeanDefination并注册到IoC容器中的. ...

  10. Codeforces Round #790 (Div. 4) A-H

    Codeforces Round #790 (Div. 4) A-H A 题目 https://codeforces.com/contest/1676/problem/A 题解 思路 知识点:模拟. ...