Spring JDBC-数据连接泄露解读
- 概述
- 示例数据连接泄露演示
- 事务环境下通过DataSourceUtils获取数据连接
- 非事务环境下通过DataSourceUtils获取数据连接也可能造成泄漏
- JdbcTemplate 如何做到对连接泄漏的免疫
- 使用 TransactionAwareDataSourceProxy
- 其它数据访问技术的等价类
- 总结
- 示例源码
概述
数据连接泄漏无疑是一个可怕的梦魇。如果存在数据连接泄漏问题,应用程序将因数据连接资源的耗尽而崩溃,甚至还可能引起数据库的崩溃。
Spring DAO 对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要我们的应用程序都使用 Spring DAO 模板(如 JdbcTemplate、HibernateTemplate 等)进行数据访问,一定不会存在数据连接泄漏的问题 。
因此,我们无需关注数据连接(Connection)及其衍生品(Hibernate 的 Session 等)的获取和释放的操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
但是由于集成第三方产品,整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
众所周知,当 Spring 事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享这个数据连接。这个数据连接从数据源获取及返回给数据源都在 Spring 掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被 Spring 管控的数据连接,则我们可以放心使用,无需关注连接释放的问题。
那如何获取这些被 Spring 管控的数据连接呢? Spring 提供了两种方法:
其一是使用数据资源获取工具类
其二是对数据源(或其衍生品如 Hibernate SessionFactory)进行代理。
示例:数据连接泄露演示
在具体介绍这些方法之前,让我们先来看一下各种引发数据连接泄漏的场景。
package com.xgj.dao.transaction.dbConnleak;import java.sql.Connection;
import java.sql.SQLException;import org.apache.commons.dbcp.BasicDataSource;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;@Service
public class JdbcStudentService {private Logger logger = Logger.getLogger(JdbcStudentService.class);private static final String addStudentSQL = "insert into student(id,name,age,sex) values(student_id_seq.nextval,?,?,?)";private JdbcTemplate jdbcTemplate;@Autowiredpublic void setJdbcTemplate(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}public void addStudent(Student student) {try {// (0)直接从数据源获取连接,后续程序没有显式释放该连接Connection connection = jdbcTemplate.getDataSource().getConnection();jdbcTemplate.update(addStudentSQL, student.getName(),student.getAge(), student.getSex());Thread.sleep(1000);// (0-1)模拟程序代码的执行时间logger.info("addStudent successfully");} catch (SQLException | InterruptedException e) {e.printStackTrace();}}}
JdbcStudentService通过 Spring AOP 事务增强的配置,让所有 public 方法都工作在事务环境中。即让addStudent()方法拥有事务功能。在 addStudent() 方法内部,我们在(0)处通过调用 jdbcTemplate.getDataSource().getConnection()显式获取一个连接,这个连接不是 addStudent() 方法事务上下文线程绑定的连接,所以如果我们如果没有手工释放这连接(显式调用 Connection#close() 方法),则这个连接将永久被占用(处于 active 状态),造成连接泄漏!
下面,我们编写模拟运行的代码,查看方法执行对数据连接的实际占用情况
// (1)以异步线程的方式执行JdbcStudentService#addStudent()方法,以模拟多线程的环境public static void asynchrLogon(JdbcStudentService userService,Student student) {StudentServiceRunner runner = new StudentServiceRunner(userService,student);runner.start();}private static class StudentServiceRunner extends Thread {private JdbcStudentService studentService;private Student student;public StudentServiceRunner(JdbcStudentService studentService,Student student) {this.studentService = studentService;this.student = student;}public void run() {studentService.addStudent(student);}}// (2) 让主执行线程睡眠一段指定的时间public static void sleep(long time) {try {Thread.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}}/*** * * @Title: reportConn* * @Description: (3)汇报数据源的连接占用情况* * @param basicDataSource* * @return: void*/public static void reportConn(BasicDataSource basicDataSource) {System.out.println("连接数[active:idle]-["+ basicDataSource.getNumActive() + ":"+ basicDataSource.getNumIdle() + "]");}public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("com/xgj/dao/transaction/dbConnleak/conf_conn_leak.xml");JdbcStudentService jdbcStudentService = (JdbcStudentService) ctx.getBean("jdbcStudentService");BasicDataSource basicDataSource = (BasicDataSource) ctx.getBean("dataSource");// (4)汇报数据源初始连接占用情况JdbcStudentService.reportConn(basicDataSource);Student student = new Student();student.setAge(20);student.setName("LEAK");student.setSex("MALE");JdbcStudentService.asynchrLogon(jdbcStudentService, student);JdbcStudentService.sleep(500);// (5)此时线程A正在执行JdbcStudentService#addStudent()方法JdbcStudentService.reportConn(basicDataSource);JdbcStudentService.sleep(2000);// (6)此时线程A所执行的JdbcStudentService#addStudent()方法已经执行完毕JdbcStudentService.reportConn(basicDataSource);JdbcStudentService.asynchrLogon(jdbcStudentService, student);JdbcStudentService.sleep(500);// (7)此时线程B正在执行JdbcStudentService#addStudent()方法JdbcStudentService.reportConn(basicDataSource);JdbcStudentService.sleep(2000);// (8)此时线程A和B都已完成JdbcStudentService#addStudent()方法的执行JdbcStudentService.reportConn(basicDataSource);}
在 JdbcStudentService中添加一个可异步执行 addStudent() 方法的 asynchrLogon() 方法,我们通过异步执行 addStudent() 以及让主线程睡眠的方式模拟多线程环境下的执行场景。在不同的执行点,通过 reportConn() 方法汇报数据源连接的占用情况。
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --><context:component-scan base-package="com.xgj.dao.transaction.dbConnleak" /><!-- 使用context命名空间,配置数据库的properties文件 --><context:property-placeholder location="classpath:spring/jdbc.properties" /><bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:driverClassName="${jdbc.driverClassName}"p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /><!-- 配置Jdbc模板 --><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"p:dataSource-ref="dataSource" /><!--事务管理器,通过属性引用数据源 --><bean id="jdbcManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSource"/><!-- 通过aop 配置事务增强 --><aop:config proxy-target-class="true"><!-- 切点 --><aop:pointcut id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.dbConnleak.JdbcStudentService)"/><!-- 切面 --><aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="txAdvice"/></aop:config><!-- 增强,供aop:advisor引用 --><tx:advice id="txAdvice" transaction-manager="jdbcManager"><tx:attributes><tx:method name="*"/></tx:attributes></tx:advice></beans>
保证 BasicDataSource 数据源的配置默认连接为 0,运行程序
2017-09-26 22:38:26,862 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4680937b: startup date [Tue Sep 26 22:38:26 BOT 2017]; root of context hierarchy
2017-09-26 22:38:26,951 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/transaction/dbConnleak/conf_conn_leak.xml]
连接数[active:idle]-[0:0]
连接数[active:idle]-[1:0]
2017-09-26 22:38:29,975 INFO [Thread-1] (JdbcStudentService.java:35) - addStudent successfully
连接数[active:idle]-[1:1]
连接数[active:idle]-[3:0]
2017-09-26 22:38:31,872 INFO [Thread-2] (JdbcStudentService.java:35) - addStudent successfully
连接数[active:idle]-[2:1]
我们通过下表对数据源连接的占用和泄漏情况进行描述
可见在执行线程 1 执行完毕后,只释放了一个数据连接,还有一个数据连处于 active 状态,说明泄漏了一个连接。相似的,执行线程 2 执行完毕后,也泄漏了一个连接:原因是直接通过数据源获取连接(jdbcTemplate.getDataSource().getConnection())而没有显式释放造成的。
事务环境下通过DataSourceUtils获取数据连接
Spring 提供了一个能从当前事务上下文中获取绑定的数据连接的工具类- DataSourceUtils。
Spring 强调必须使用 DataSourceUtils 工具类获取数据连接,Spring 的 JdbcTemplate 内部也是通过 DataSourceUtils 来获取连接的。
DataSourceUtils 提供了若干获取和释放数据连接的静态方法
static Connection doGetConnection(DataSource
:首先尝试从事务上下文中获取连接,失败后再从数据源获取连接;
dataSource)static Connection getConnection(DataSource dataSource)
:和doGetConnection 方法的功能一样,实际上,它内部就是调用 doGetConnection 方法获取连接的;static void doReleaseConnection(Connection con, DataSourcedataSource)
:释放连接,放回到连接池中;static void releaseConnection(Connection con, DataSource
:和 doReleaseConnection 方法的功能一样,实际上,它内部就是调用 doReleaseConnection 方法获取连接的;
dataSource)
来看一下 DataSourceUtils 从数据源获取连接的关键代码:
public abstract class DataSourceUtils {…public static Connection doGetConnection(DataSource dataSource) throws SQLException {Assert.notNull(dataSource, "No DataSource specified");//①首先尝试从事务同步管理器中获取数据连接ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested();if (!conHolder.hasConnection()) {logger.debug("Fetching resumed JDBC Connection from DataSource");conHolder.setConnection(dataSource.getConnection());}return conHolder.getConnection();}//②如果获取不到,则直接从数据源中获取连接Connection con = dataSource.getConnection();//③如果拥有事务上下文,则将连接绑定到事务上下文中if (TransactionSynchronizationManager.isSynchronizationActive()) {ConnectionHolder holderToUse = conHolder;if (holderToUse == null) {holderToUse = new ConnectionHolder(con);}else {holderToUse.setConnection(con);}holderToUse.requested();TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));holderToUse.setSynchronizedWithTransaction(true);if (holderToUse != conHolder) {TransactionSynchronizationManager.bindResource(dataSource, holderToUse);}}return con;}…
}
它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。
我们对上面那个有连接泄露的方法进行改造,使用 DataSourceUtils.getConnection() 替换直接从数据源中获取连接的代码:
public void addStudent(Student student) {try {// (0)直接从数据源获取连接,后续程序没有显式释放该连接// Connection connection = jdbcTemplate.getDataSource()// .getConnection();// 在事务环境下,通过DataSourceUtils获取数据连接Connection coon = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());jdbcTemplate.update(addStudentSQL, student.getName(),student.getAge(), student.getSex());Thread.sleep(1000);// (0-1)模拟程序代码的执行时间logger.info("addStudent successfully");} catch (Exception e) {e.printStackTrace();}}
重新运行日志如下:
2017-09-26 23:19:32,588 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2c686c5e: startup date [Tue Sep 26 23:19:32 BOT 2017]; root of context hierarchy
2017-09-26 23:19:32,719 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/transaction/dbConnleak/conf_conn_leak.xml]
连接数[active:idle]-[0:0]
连接数[active:idle]-[0:0]
2017-09-26 23:19:36,716 INFO [Thread-1] (JdbcStudentService.java:40) - addStudent successfully
连接数[active:idle]-[0:1]
连接数[active:idle]-[1:0]
2017-09-26 23:19:38,273 INFO [Thread-2] (JdbcStudentService.java:40) - addStudent successfully
连接数[active:idle]-[0:1]
我们可以看到已经没有连接泄漏的现象了。一个执行线程在运行 JdbcStudentService#addStudent() 方法时,只占用一个连接,而且方法执行完毕后,该连接马上释放。这说明通过 DataSourceUtils.getConnection() 方法确实获取了方法所在事务上下文绑定的那个连接,而不是像原来那样从数据源中获取一个新的连接。
非事务环境下通过DataSourceUtils获取数据连接也可能造成泄漏
如果 DataSourceUtils 在没有事务上下文的方法中使用 getConnection() 获取连接,依然会造成数据连接泄漏!
我们保持使用DataSourceUtils获取数据源的代码不变,修改下配置文件中的AOP增强,去掉事务增强(如下部分)
<!-- 通过aop 配置事务增强 --><aop:config proxy-target-class="true"><!-- 切点 --><aop:pointcut id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.dbConnleak.JdbcStudentService)"/><!-- 切面 --><aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="txAdvice"/></aop:config><!-- 增强,供aop:advisor引用 --><tx:advice id="txAdvice" transaction-manager="jdbcManager"><tx:attributes><tx:method name="*"/></tx:attributes></tx:advice>
再此运行
2017-09-26 23:23:04,538 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7ba2a618: startup date [Tue Sep 26 23:23:04 BOT 2017]; root of context hierarchy
2017-09-26 23:23:04,655 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/transaction/dbConnleak/conf_conn_leak.xml]
连接数[active:idle]-[0:0]
连接数[active:idle]-[0:0]
2017-09-26 23:23:07,759 INFO [Thread-1] (JdbcStudentService.java:40) - addStudent successfully
连接数[active:idle]-[1:1]
连接数[active:idle]-[2:1]
2017-09-26 23:23:09,504 INFO [Thread-2] (JdbcStudentService.java:40) - addStudent successfully
连接数[active:idle]-[2:1]
有事务上下文时,需要等到整个事务方法(即 addStudent())返回后,事务上下文绑定的连接才释放。但在没有事务上下文时,addStudent() 调用 JdbcTemplate 执行完数据操作后,马上就释放连接。
为了避免这种情况,需要进行如下改造
public void addStudent(Student student) {Connection conn = null;try {// 在非事务环境下,通过DataSourceUtils获取数据连接conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());jdbcTemplate.update(addStudentSQL, student.getName(),student.getAge(), student.getSex());Thread.sleep(1000);// (0-1)模拟程序代码的执行时间logger.info("addStudent successfully");// (1)} catch (Exception e) {e.printStackTrace();} finally {// 必须显式使用DataSourceUtils释放连接,否则造成了解泄露DataSourceUtils.releaseConnection(conn,jdbcTemplate.getDataSource());}}
显式调用 DataSourceUtils.releaseConnection() 方法释放获取的连接。特别需要指出的是:一定不能在 (1)处释放连接!因为如果 addStudent() 在获取连接后,(1)处代码前这段代码执行时发生异常,则(1)处释放连接的动作将得不到执行。这将是一个非常具有隐蔽性的连接泄漏的隐患点。
JdbcTemplate 如何做到对连接泄漏的免疫
分析 JdbcTemplate 的代码,我们可以清楚地看到它开放的每个数据操作方法,首先都使用 DataSourceUtils 获取连接,在方法返回之前使用 DataSourceUtils 释放连接。
来看一下 JdbcTemplate 最核心的一个数据操作方法 execute():
public <T> T execute(StatementCallback<T> action) throws DataAccessException {//① 首先根据DataSourceUtils获取数据连接Connection con = DataSourceUtils.getConnection(getDataSource());Statement stmt = null;try {Connection conToUse = con;…handleWarnings(stmt);return result;}catch (SQLException ex) {JdbcUtils.closeStatement(stmt);stmt = null;DataSourceUtils.releaseConnection(con, getDataSource());con = null;throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);}finally {JdbcUtils.closeStatement(stmt);//② 最后根据DataSourceUtils释放数据连接DataSourceUtils.releaseConnection(con, getDataSource());}
}
在 ① 处通过 DataSourceUtils.getConnection() 获取连接,在 ② 处通过 DataSourceUtils.releaseConnection() 释放连接。
所有 JdbcTemplate 开放的数据访问方法最终都是通过 execute(StatementCallback<T> action)
执行数据访问操作的,因此这个方法代表了 JdbcTemplate 数据操作的最终实现方式。
正是因为 JdbcTemplate 严谨的获取连接,释放连接的模式化流程保证了 JdbcTemplate 对数据连接泄漏问题的免疫性。所以,如有可能尽量使用 JdbcTemplate,HibernateTemplate 等这些模板进行数据访问操作,避免直接获取数据连接的操作。
使用 TransactionAwareDataSourceProxy
如果不得已要显式获取数据连接,除了使用 DataSourceUtils 获取事务上下文绑定的连接外,还可以通过 TransactionAwareDataSourceProxy 对数据源进行代理。数据源对象被代理后就具有了事务上下文感知的能力,通过代理数据源的 getConnection() 方法获取的连接和使用 DataSourceUtils.getConnection() 获取连接的效果是一样的。
下面是使用 TransactionAwareDataSourceProxy 对数据源进行代理的配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close" p:driverClassName="${jdbc.driverClassName}"p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /><!-- ①对数据源进行代理-->
<bean id="dataSourceProxy"class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"p:targetDataSource-ref="dataSource"/><!-- ②直接使用数据源的代理对象-->
<bean id="jdbcTemplate"class="org.springframework.jdbc.core.JdbcTemplate"p:dataSource-ref="dataSourceProxy"/><!-- ③直接使用数据源的代理对象-->
<bean id="jdbcManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="dataSourceProxy"/>
对数据源进行代理后,我们就可以通过数据源代理对象的 getConnection() 获取事务上下文中绑定的数据连接了。
因此,如果数据源已经进行了 TransactionAwareDataSourceProxy 的代理,而且方法存在事务上下文,那么最开始的代码也不会生产连接泄漏的问题。
其它数据访问技术的等价类
Spring 为每个数据访问技术框架都提供了一个获取事务上下文绑定的数据连接(或其衍生品)的工具类和数据源(或其衍生品)的代理类。
DataSourceUtils 的等价类
数据访问框架 | 连接获取工具类 |
---|---|
SpringJDBC/ MyBatis | org.springframework.jdbc.datasource.DataSourceUtils |
Hibernate | org.springframework.orm.hibernateX.SessionFactoryUtils |
JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
TransactionAwareDataSourceProxy 的等价类
数据访问框架 | 连接获取工具类 |
---|---|
SpringJDBC/MyBatis | org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy |
Hibernate | org.springframework.orm.hibernateX.LocalSessionFactoryBean |
JPA | org.springframework.orm.jpa.EntityManagerFactoryUtils |
JDO | 无 |
总结
使用 Spring JDBC 时如果直接获取 Connection,可能会造成连接泄漏。为降低连接泄漏的可能,尽量使用 DataSourceUtils 获取数据连接。也可以对数据源进行代理,以便将其拥有事务上下文的感知能力;
可以将 Spring JDBC 防止连接泄漏的解决方案平滑应用到其它的数据访问技术框架中
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
Spring JDBC-数据连接泄露解读相关推荐
- 使用Spring JDBC框架连接并操作数据库
在前一篇博文JAVA通过JDBC连接并操作MySQL数据库中,我们知道如何通过JDBC连接并操作数据库,但是请看程序,整个程序连接数据库和关闭数据库占了很大一部分代码量,而且每次我们执行一下数据库操作 ...
- Spring4.X系列之Spring JDBC
专栏 导读 源码 专栏 欢迎关注 : Spring-JDBC手札 导读 Spring JDBC-Spring对DAO的支持 Apache-DBCP数据库连接池解读 C3P0-数据库连接池解读 Spri ...
- Spring JDBC-使用Spring JDBC获取本地连接对象以及操作BLOB/CLOB类型数据
概述 如何获取本地数据连接 示例从DBCP数据源中获取Oracle的本地连接对象 相关接口操作 LobCreator LobHandler 插入LOB类型的数据 以块数据的方式读取LOB数据 以流数据 ...
- 使用Spring JDBC进行数据访问 (JdbcTemplate/NamedParameterJdbcTemplate/SimpleJdbcTemplate/SimpleJdbcCall/Stor)
http://www.cnblogs.com/webcc/archive/2012/04/11/2442680.html 使用Spring JDBC进行数据访问 11.1. 简介 Spring JDB ...
- JDBC笔记02-数据库连接池 Spring JDBC
今日内容 数据库连接池 Spring JDBC : JDBC Template 数据库连接池 概念: 其实就是一个容器(集合),存放数据库连接的容器 当系统初始化好后,容器被创建,容器中会申请一些连接 ...
- spring boot demo(spring jdbc访问数据)
Accessing Relational Data using JDBC with Spring 您将使用Spring JdbcTemplate 构建应用,访问数据库中数据. 下面的简单数据访问逻辑, ...
- Spring Boot之jdbc数据操作06
Spring Boot之jdbc数据操作06 JDBC 通过快速创建spring boot项目选择 mysql 和jdbc 创建一个基于web的spring boot项目 依赖为 <depend ...
- 大数据笔记16—java基础篇12(JDBC 、连接池、事务)
目录 JDBC jdbc概述 jdbc入门案例 API详解 jdbc工具类 预编译执行平台 1.SQL注入问题(安全问题) 2API详解:预处理对象(PreparedStatement) 使用连接池重 ...
- Java回顾(十二) File类、Druid连接池、JDBCTemplate(Spring JDBC)、HTML和CSS
1.File类 1.1.File类概述和构造方法 File:是文件和目录路径名的抽象表示 文件和路径是可以通过File封装为对象的 以下是三种实现的方法,一般来说,用第一种就可以 public cla ...
最新文章
- 【Go语言】【12】GO语言的结构体
- python批量读取csv文件-Python读取/批量读取文件
- Markdown 学习笔记
- 二维数组的查找 java_二维数组中的查找
- Ubuntu16.04安装PCL库的python实现python-pcl,并且通过pcl加载pcd文件验证python-pcl是否安装成功
- 在命令行中打开远程端的图形应用程序
- JQ 全选后获取选中的值_JQ完全学习版本
- [Swift]LeetCode884. 两句话中的不常见单词 | Uncommon Words from Two Sentences
- 【C/C++】值传递和址传递区别解析
- HDU 1754 I hate it【线段树之单点替换区间最值】
- .net mvc web api 返回 json 内容,过滤值为null的属性
- c语言sigaction,C语言中的Sigaction和setitimer
- [杂]实用工具与链接
- nodejs中art-template模板语法冲突解决方案
- 阿里云ECS服务器退订
- 《安富莱嵌入式周报》第213期:2021.05.17--2021.05.23
- 对接LeetCode-完成代码提交校验
- SkeyeVSS实现RTSP、Onvif监控摄像头网页无插件化直播监控解决方案
- js下载文件防止白屏
- 自带流量的免费微信编辑器推荐
热门文章
- 训练超参数, 出现 Cannot use GPU in CPU-only Caffe 错误?
- 网页拼图游戏html代码,网页设计(一):拼图游戏
- nio2 java_java NIO2(file io)
- statemodels 笔记: lowess
- 文巾解题 26. 删除有序数组中的重复项
- 数据中台推荐系统入门(二):两种经典的推荐算法
- tensorflow从入门到精通100讲(五)-知识图谱( Knowledge Graph)关系抽取之PCNN
- 本地maven仓库_Maven(四):仓库
- Matplotlib实例教程(十一)堆栈图
- 资源跳转--response重定向和request转发