原文地址:https://my.oschina.net/u/218421/blog/38513

Spring提供了两种使用JDBC API的最佳实践,一种是以JdbcTemplate为核心的基于Template的JDBC的使用方式,另一种则是在JdbcTemplate基础之上的构建的基于操作对象的JDBC的使用方式。

基于Template的JDBC的使用方式
该使用方式的最初设想和原型,需要追溯到Rod Johnson在03年出版的Expert One-on-One J2EE Design and Development,在该书的Practical Data Access(数据访问实践)中,Rod针对JDBC使用中的一些问题提出了一套改进的实践原型,并最终将该原型完善后在Spring框架中发布。

JDBC的尴尬
JDBC作为Java平台的访问关系数据库的标准,其成功是 有目共睹的。几乎所有java平台的数据访问,都直接或者间接的使用了JDBC,它是整个java平台面向关系数据库进行数据访问的基石。
作为一个标准,无疑JDBC是很成功的,但是要说JDBC在使用过程当中多么的受人欢迎,则不尽然了。JDBC主要是面向较为底层的数据库操作,所以在设计的过程当中 ,比较的贴切底层以提供尽可能多的功能特色。从这个角度来说,JDBC API的设计无可厚非。可是,过于贴切底层的API的设计,对于开发人员则未必是一件好事。即使执行一个最简单的查询,开发人员也要按照API的规矩写上一大堆雷同的代码,如果不能合理的封装使用JDBC API,在项目中使用JDBC访问数据所出现的问题估计会使人发疯!
对于通常的项目开发来说,如果层次划分很明确,数据访问逻辑一般应该在DAO层中实现。根据功能模块的划分,可能每个开发人员都会分得或多或少的实现相应的DAO的任务,假设开发人员A在分得了DAO实现任务后进行开发,他或许开发了如下所示的代码:

package com.google.spring.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement; import javax.sql.DataSource; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; public class DaoWithA implements IDao
{ private final Log logger = LogFactory.getLog(DaoWithA.class);
private DataSource dataSource = null; public DataSource getDataSource()
{
return dataSource;
} public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
} @Override
public int updateSomething(String sql)
{
int count;
Connection conn = null;
Statement stmt = null;
try
{
conn = getDataSource().getConnection();
stmt = conn.createStatement();
count = stmt.executeUpdate(sql);
stmt.close();
stmt = null;
}
catch (SQLException e)
{
throw new RuntimeException(e); }
finally
{
if(stmt!=null)
{
try
{
stmt.close();
}
catch (SQLException ex)
{
logger.warn("fail to close statement:"+ex);
}
}
if(conn!=null)
{
try
{
conn.close();
}
catch (Exception ex)
{
logger.warn("failed to close Connection:"+ex);
}
}
}
return count;
} }

而B所负责的DAO的实现中,可能也有类似的更新的操作。无疑,B也要像A这样,在他的DAO实现类中写一大堆同样的JDBC代码,类似的情况还可能扩展到C、D等开发人员。如果每个开发人员都能严格的按照JDBC的编程规范开发还好,但是事实是,一个团队中的开发人员是有差别的。
这其实只是API的使用过程中的一个插曲,当你看到应用程序中成百的使用JDBC实现类的时候,会发现如下的问题:
1、Statement使用完没有关闭,而是想着让Connection关闭的时候一并关闭,可是并非所有的驱动程序都有这样的行为。
2、创建了多个ResultSet或者Statement,只清理了最外层的,忽视了里层的。

3、忘记关闭Connection。
JDBC规范在指定数据库访问异常的时候也没有能够进行的很彻底:
1、将异常类型定义为SQLException是一个值得商榷的地方。
2、SQLExcpetion没有采用将具体的异常情况子类化,以进一步抽象不同的数据访问的情况,而是采用ErrorCode的方式来区分访问过程中所出现的不同异常情况,其实这也没什么,只要能区分出具体的错误就行,但是JDBC规范却把ErrorCode的规范留给了数据库提供商,这导致了不同的数据库供应商对应了不同的ErrorCode,进而应用程序在捕获到SQLException后,还要看当前用的是什么数据库。
针对以上问题,Spring提供了相应的解决方案帮助我们提高开发效率!

为了解决JDBC API在实际使用中的各种尴尬的局面,spring提出了org.springframework.jdbc.core.JdbcTemplate作为数据访问的Helper类。JdbcTemplate是整个spring数据抽象层提供的所有JDBC API最佳实践的基础,框架内其它更加方便的Helper类以及更高层次的抽象,全部的构建于JdbcTemplate之上。抓住了JdbcTemplate,就抓住了spring框架JDBC API最佳实践的核心。
概括的说,JdbcTemplate主要关注一下两个事情:
1、封装所有的基于JDBC的数据访问的代码,以统一的格式和规范来使用JDBC API。所有的基于JDBC API的数据访问全部通过JdbcTemplate,从而避免了容易出错的数据访问方式。
2、对SQLException所提供的异常信息在框架内进行统一的转译,将基于JDBC的数据访问异常纳入Spring自身的异常层次之中,统一了数据接口的定义,简化了客户端代码对数据访问异常的处理。
Spring主要是通过模板方法对基于JDBC的数据访问代码进行统一的封装,所以我们可先看下模板方法:
模板方法主要是用于对算法的行为或者逻辑进行封装,即如果多个类中存在相似的算法逻辑或者行为逻辑,可以将这些逻辑提取到模板方法中实现,然后让相应的子类根据需要实现某些自定义的逻辑。
举个例子,所有的汽车,不管是宝马还是大众,他们的驾驶流程基本上是固定的。实际上,除了少数的实现细节有所不同之外,大部分的流程是相同的,基本上是如下所示的流程说明:
1、点火启动
2、踩刹车,挂前进的档位(不同的车在这一步会存在差异)
3、放下手动控制器(手刹)
4、踩油门启动车辆运行
此时,我们可以声明一个模板方法类,将确定的行为以模板的形式定义,而将不同的行为留给相应的子类来实现:

package com.google.spring.jdbc;

public abstract class Vehicle
{ public final void drive()
{
startTheEnginee();//启动
putIntoGear(); //前进
looseHandBrake();//放下手刹
stepOnTheGasAndGo();//踩油门前进
} protected abstract void putIntoGear(); private void startTheEnginee()
{ } private void looseHandBrake()
{ } private void stepOnTheGasAndGo()
{ }
}

drive()方法就是我们的模板方法,它被声明为final,表示该类是不能被子类重写的,车辆的自动挡和手动挡是不同的,所以留给了子类去实现:

package com.google.spring.jdbc;

public class VehicleAT extends Vehicle
{ @Override
protected void putIntoGear()
{
//挂前进档位 } }
package com.google.spring.jdbc;

public class VehicleMT extends Vehicle
{ @Override
protected void putIntoGear()
{
//踩离合器 挂前进档位 } }

这样,每个子类实现特有的逻辑就可以了。

JdbcTemplate的演化
如果回头看一下最初的使用JDBC API进行数据访问的代码。就会发现,不管这些代码是谁负责的,也不管数据访问的逻辑如何,除了小部分的差异之外,所有的这些代码几乎都是按照同一个流程走下来的,如下:
1、conn=getDataSource().getConnection();
2、stmt=conn.createStatement()或者ps=conn.prepareStatement();
3、stmt.executeUpdate(sql)或者ps.executeUpdate()  或者进行相应的查询。
4、stmt.close()  stmt=null
5、catch处理数据库访问异常
6、关闭数据库连接避免连接泄露导致系统崩溃
对于多个DAO中充斥着几乎相同的JDBC API的使用代码,我们也可以采用模板方法,多这些代码进行重构,避免因个人操作不当所出现的种种问题,我们要做的,就是将一些公共的行为提取到模板方法中去,而特有的操作,比如每次执行不同的更新,或者对不同的查询结果进行不同的处理,则放入具体的子类中,这样,我们就有个JdbcTemplate的雏形:

package com.google.spring.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement; import javax.sql.DataSource; import org.springframework.dao.DataAccessException; public abstract class JdbcTemplate
{
private DataSource dataSource; public DataSource getDataSource()
{
return dataSource;
} public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
} public final Object execute(String sql)
{
Connection conn = null;
Statement stmt = null;
try
{
conn = this.getDataSource().getConnection();
stmt = conn.createStatement();
Object retValue = this.executeWithStatement(stmt, sql);
return retValue;
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
finally
{
closeStatement(stmt);
closeConnection(conn);
}
} protected abstract Object executeWithStatement(Statement stmt,String sql); private final DataAccessException translateSQLException(SQLException e)
{
DataAccessException dataAccessException = null;
//进行相应的转译
return dataAccessException;
} private final void closeStatement(Statement stmt)
{
//关闭Statement
} private final void closeConnection(Connection conn)
{
//关闭Connection
}
}

这样处理之后,JDBC代码的使用有了规范。但是,只使用模板方法还不足以提供方便的Helper类。顶着abstract的帽子,每次使用都要进行相应的子类化,这也太不靠谱了。所以,spring中的JdbcTemplate除了引入了模板方法之外,还引入了相应的Callback,避免了每次都子类化,比如,当引入了StatementCallback接口以后:

package com.google.spring.jdbc;

import java.sql.Statement;

public interface StatementCallback
{
public Object doWithStatement(Statement stmt) throws SQLException;
}

这样这个真正的Helper类就存在了,如下所示:

package com.google.spring.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement; import javax.sql.DataSource; import org.springframework.dao.DataAccessException; public class JdbcTemplate
{
private DataSource dataSource; public DataSource getDataSource()
{
return dataSource;
} public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
} public final Object execute(StatementCallback callback)
{
Connection conn = null;
Statement stmt = null;
try
{
conn = this.getDataSource().getConnection();
stmt = conn.createStatement();
Object retValue = callback.doWithStatement(stmt);
return retValue;
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
finally
{
closeStatement(stmt);
closeConnection(conn);
}
} private final DataAccessException translateSQLException(SQLException e)
{
DataAccessException dataAccessException = null;
//进行相应的转译
return dataAccessException;
} private final void closeStatement(Statement stmt)
{
//关闭Statement
} private final void closeConnection(Connection conn)
{
//关闭Connection
}
}

要在相应的DAO中使用该JdbcTemplate,只需要根据情况提供参数和相应的callback就可以了,如下所示:

final String sql = "update";
JdbcTemplate jdbcTemplate = new JdbcTemplate();
BasicDataSource dataSource = new BasicDataSource();
jdbcTemplate.setDataSource(dataSource);
//对dataSource进行setter操作
StatementCallback callback = new StatementCallback()
{
public Object doWithStatement(Statement stmt) throws SQLException
{
return new Integer(stmt.executeUpdate(sql)) ;
}
}; jdbcTemplate.execute(callback);

这样,开发人员只需要关注与数据访问逻辑相关的东西,JDBC底层的细节不需要再考虑了。
上述是spring中JdbcTemplate的中心思想,实际上,JdbcTemplate在实现上要考虑很多的东西,继承层次如下:

org.springframework.jdbc.core.JdbcOperations接口定义了JdbcTemplate可以使用的JDBC操作集合,该接口提供的操作声明,从查询到更新无所不有。
JdbcTemplate的直接父类是JdbcAccessor,这是一个抽象类,主要为子类提供一些公用的属性:

DataSource:javax.sql.DataSource是JDBC2.0之后引入的接口定义,用来替代java.sql.DriverManager的数据库连接方式,它的角色可以看做是JDBC的连接工厂,所以,基本上现在它应该作为获取数据库资源的统一接口。

SQLExceptionTranslator:JdbcTemplate委托此类进行异常的转译。

JdbcTemplate中的模板方法可分为如下的四组:
面向Connection的模板方法:
通过ConnectionCallback接口所公开的Connection进行数据访问

import java.sql.Connection;
import java.sql.SQLException; import org.springframework.dao.DataAccessException; public interface ConnectionCallback
{
Object doInConnection(Connection con) throws SQLException, DataAccessException;
}
public Object execute(ConnectionCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
return action.doInConnection(conToUse); }
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

可以随意操作Connection。
面向Statement的模板方法:
该模板方法主要处理基于SQL的数据访问请求。该组模板方法通过org.springframework.jdbc.core.StatementCallback回调接口,对外公开java.sql.Statement的操作句柄。该方式缩小了回调接口内的权限范围,但是提高了API使用上的安全性和便捷性。
面向PreparedStatement的模板方法:
对于使用包含查询参数的SQL请求来说,使用PreparedStatement可以让我们免于SQL注入的攻击,而在使用PreparedStatement之前,需要根据传入的包含参数的SQL对其进行创建,所以,面向PreparedStatement的模板方式会通过org.springframework.jdbc.core.PreparedStatementCreator的回调接口公开Connection以允许PreparedStatement的创建。PreparedStatement创建之后,会公开org.springframework.jdbc.core.PreparedStatementCallback回调接口,以支持其使用PreparedStatement进行数据访问。
面向CallableStatement的模板方法:
JDBC支持使用CallableStatement进行数据库存储过程的访问,面向CallableStatement的的模板方法会通过org.springframework.jdbc.core.CallableStatementCreator公开的Connection用于创建调用存储过程的CallableStatement。之后,再通过org.springframework.jdbc.core.CallableStatementCallback公开的CallableStatement的操作句柄,实现基于存储过程的数据访问。
每一组的模板方法都 有一个核心的方法实现,其它的属于同一组的重载的模板方法,会调用这个核心的方法来完成最终的工作。以面向Statement的模板方法为例,使用StatementCallback回调接口作为方法参数的execute方法是这组的核心代码:

public Object execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
Object result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

其它模板方法会根据自身的签名,构建相应的StatementCallback实例以调用回调接口中公开的方法,例如:

public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
} class ExecuteStatementCallback implements StatementCallback, SqlProvider {
public Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}

同一组内的模板方法,可以根据使用的方便性进行增加,只要在实现的时候,将相应的条件加以对应,改组的回调接口进行封装,最终调用当前组的核心模板方法即可。
下面来逐一看下这些方法:
public void execute(final String sql):

public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
} class ExecuteStatementCallback implements StatementCallback, SqlProvider {
public Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}

根据传入的静态SQL语句进行更新,无返回值,使用的是Statement

public Object execute(String callString, CallableStatementCallback action) throws DataAccessException:

public Object execute(String callString, CallableStatementCallback action) throws DataAccessException {
return execute(new SimpleCallableStatementCreator(callString), action);
}

内部调的是

public Object execute(CallableStatementCreator csc, CallableStatementCallback action)
throws DataAccessException { Assert.notNull(csc, "CallableStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(csc);
logger.debug("Calling stored procedure" + (sql != null ? " [" + sql + "]" : ""));
} Connection con = DataSourceUtils.getConnection(getDataSource());
CallableStatement cs = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
cs = csc.createCallableStatement(conToUse);
applyStatementSettings(cs);
CallableStatement csToUse = cs;
if (this.nativeJdbcExtractor != null) {
csToUse = this.nativeJdbcExtractor.getNativeCallableStatement(cs);
}
Object result = action.doInCallableStatement(csToUse);
handleWarnings(cs);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (csc instanceof ParameterDisposer) {
((ParameterDisposer) csc).cleanupParameters();
}
String sql = getSql(csc);
csc = null;
JdbcUtils.closeStatement(cs);
cs = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("CallableStatementCallback", sql, ex);
}
finally {
if (csc instanceof ParameterDisposer) {
((ParameterDisposer) csc).cleanupParameters();
}
JdbcUtils.closeStatement(cs);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

JdbcTemplate在此提供了一个内部类SimpleCallableStatementCreator:

private static class SimpleCallableStatementCreator implements CallableStatementCreator, SqlProvider {

        private final String callString;

        public SimpleCallableStatementCreator(String callString) {
Assert.notNull(callString, "Call string must not be null");
this.callString = callString;
} public CallableStatement createCallableStatement(Connection con) throws SQLException {
return con.prepareCall(this.callString);
} public String getSql() {
return this.callString;
}
}

根据传入的sql语句创建一CallableStatement

public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException

public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action);
}

可知其内部调用了
execute(new SimplePreparedStatementCreator(sql), action)方法。

public Object execute(PreparedStatementCreator psc, PreparedStatementCallback action)
throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
} Connection con = DataSourceUtils.getConnection(getDataSource());
PreparedStatement ps = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
ps = psc.createPreparedStatement(conToUse);
applyStatementSettings(ps);
PreparedStatement psToUse = ps;
if (this.nativeJdbcExtractor != null) {
psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
}
Object result = action.doInPreparedStatement(psToUse);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

在创建PreparedStatementCreator实现类的时候,JdbcTemplate为其默认提供了一个SimplePreparedStatementCreator内部静态类,可根据传入的SQL语句创建一个PreparedStatement  代码如下:

private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider {

        private final String sql;

        public SimplePreparedStatementCreator(String sql) {
Assert.notNull(sql, "SQL must not be null");
this.sql = sql;
} public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
return con.prepareStatement(this.sql);
} public String getSql() {
return this.sql;
}
}

其它的模仿方法与此类似,可以触类旁通!

Spring JDBC最佳实践(1)的更多相关文章

  1. Spring JDBC最佳实践(3)

    原文地址:https://my.oschina.net/u/218421/blog/38598 spring jdbc包提供了JdbcTemplate和它的两个兄弟SimpleJdbcTemplate ...

  2. Spring JDBC最佳实践(2)

    原文地址:https://my.oschina.net/u/218421/blog/38576 使用DataSourceUtils进行Connection的管理由上节代码可知,JdbcTemplate ...

  3. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    之前也写过一篇关于Spring Validation使用的文章,不过自我感觉还是浮于表面,本次打算彻底搞懂Spring Validation.本文会详细介绍Spring Validation各种场景下 ...

  4. 微服务电商项目发布重大更新,打造Spring Cloud最佳实践!

    Spring Cloud实战电商项目mall-swarm地址:转发+关注 私信我获取地址 系统架构图   系统架构图 项目组织结构 mall├── mall-common-- 工具类及通用代码模块├─ ...

  5. Spring Boot 16 条最佳实践

    Spring Boot是最流行的用于开发微服务的Java框架.在本文中,我将与你分享自2016年以来我在专业开发中使用Spring Boot所采用的最佳实践.这些内容是基于我的个人经验和一些熟知的Sp ...

  6. 面试题_76_to_81_Java 最佳实践的面试问题

    包含 Java 中各个部分的最佳实践,如集合,字符串,IO,多线程,错误和异常处理,设计模式等等. 76)Java 中,编写多线程程序的时候你会遵循哪些最佳实践?(答案)这是我在写Java 并发程序的 ...

  7. 开涛spring3(7.5) - 对JDBC的支持 之 7.5 集成Spring JDBC及最佳实践

    7.5 集成Spring JDBC及最佳实践 大多数情况下Spring JDBC都是与IOC容器一起使用.通过配置方式使用Spring JDBC. 而且大部分时间都是使用JdbcTemplate类(或 ...

  8. Spring Boot学习笔记2——基本使用之最佳实践[z]

    前言 在上一篇文章Spring Boot 学习笔记1——初体验之3分钟启动你的Web应用已经对Spring Boot的基本体系与基本使用进行了学习,本文主要目的是更加进一步的来说明对于Spring B ...

  9. spring boot 使用及最佳实践

    第一部分,spring boot 文档 Spring boot的使用 使用maven进行构建 用户可以通过继承spring-boot-starter-parent来获取默认的依赖. l  默认java ...

随机推荐

  1. Power BI连接Oracle的注意事项

    开始 Power BI 连接Oracle需要安装对应位数的ODAC,这个过程中有几个点要注意. ODAC 12c.x 版本(32.64),在安装时要将GAC的勾搭上.否则打开Power BI时会提示找 ...

  2. 我是如何一步步编码完成万仓网ERP系统的(八)产品库设计 4.品牌类别

    https://www.cnblogs.com/smh188/p/11533668.html(我是如何一步步编码完成万仓网ERP系统的(一)系统架构) https://www.cnblogs.com/ ...

  3. RabbitMQ高级面试题

    本文涉及:投递失败的消息怎么处理.如何实现延时队列.如何指定消息的优先级.消息的持久化是如何实现的.如何保证消息不丢失 投递失败的消息怎么处理 首先投递失败存在如下两个情况 当交换器无法根据自身的类型 ...

  4. 不要在 MySQL 中使用“utf8”,请使用“utf8mb4”

    不要在 MySQL 中使用“utf8”,请使用“utf8mb4” 最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误: ...

  5. Java中的equals与==

    package demo; public class Test { public static void main(String[] args) { String str1 = new String( ...

  6. 使用paginate方法分页无法判断获取的数据是否为空

    问题:使用paginate方法分页无法判断获取的数据是否为空,在模板里面无法判断数据是否为空,比如在商品列表当中,当没有商品时无法判断生成的对象为空,所有就什么都不显示了. 解决办法: $newsDa ...

  7. centos7 安装 ffmpeg

    升级系统 yum install epel-release -yyum update -y 安装Nux Dextop Yum源 由于centos 没有官方软件包,我们可以使用第三方YUM源(Nux D ...

  8. Python实现感知器的逻辑电路(与门、与非门、或门、异或门)

    在神经网络入门回顾(感知器.多层感知器)中整理了关于感知器和多层感知器的理论,这里实现关于与门.与非门.或门.异或门的代码,以便对感知器有更好的感觉. 此外,我们使用 pytest 框架进行测试. p ...

  9. python基本数据类型的时间复杂度

    1.list 内部实现是数组 2.dict 内部实现是hash函数+哈希桶.一个好的hash函数使到哈希桶中的值只有一个,若多个key hash到了同一个哈希桶中,称之为哈希冲突. 3.set 内部实 ...

  10. 201871020225-牟星源 《面向对象程序设计(java)》课程学习进度条

    <2019面向对象程序设计(java)课程学习进度条> 周次 (阅读/编写)代码行数 发布博客量/评论他人博客数量 课余学习时间(小时) 学习收获最大的程序 阅读或编译让我 第一周 25/ ...