《Spring 5 官方文档》15.使用JDBC实现数据访问

《Spring 5 官方文档》15.使用JDBC实现数据访问

15.1 介绍Spring JDBC框架

表格13.1很清楚的列举了Spring框架针对JDBC操作做的一些抽象和封装。里面区分了哪些操作Spring已经帮你做好了、哪些操作是应用开发者需要自己负责的.

表13.1. Spring JDBC – 框架和应用开发者各自分工

操作 Spring 开发者
定义连接参数   X
打开连接 X  
指定SQL语句   X
声明参数和提供参数值   X
准备和执行语句 X  
返回结果的迭代(如果有) X  
具体操作每个迭代   X
异常处理 X  
事务处理 X  
关闭连接、语句和结果集 X  

一句话、Spring帮你屏蔽了很多JDBC底层繁琐的API操作、让你更方便的开发

15.1.1 选择一种JDBC数据库访问方法

JDBC数据库访问有几种基本的途径可供选择。除了JdbcTemplate的三种使用方式外,新的SimpleJdbcInsert和SimplejdbcCall调用类通过优化数据库元数据(来简化JDBC操作),还有一种更偏向于面向对象的RDBMS对象风格的方法、有点类似于JDO的查询设计。即使你已经选择了其中一种方法、你仍然可以混合使用另外一种方法的某一个特性。所有的方法都需要JDBC2.0兼容驱动的支持,一些更高级的特性则需要使用JDBC3.0驱动支持。

  • JdbcTemplate 是经典的Spring JDBC访问方式,也是最常用的。这是“最基础”的方式、其他所有方式都是在 JdbcTemplate的基础之上封装的。
  • NamedParameterJdbcTemplate 在原有JdbcTemplate的基础上做了一层包装支持命名参数特性、用于替代传统的JDBC“?”占位符。当SQL语句中包含多个参数时使用这种方式能有更好的可读性和易用性
  • SimpleJdbcInsert和SimpleJdbcCall操作类主要利用JDBC驱动所提供的数据库元数据的一些特性来简化数据库操作配置。这种方式简化了编码、你只需要提供表或者存储过程的名字、以及和列名相匹配的参数Map。但前提是数据库需要提供足够的元数据。如果数据库没有提供这些元数据,需要开发者显式配置参数的映射关系。
  • RDBMS对象的方式包含MappingSqlQuery, SqlUpdate和StoredProcedure,需要你在初始化应用数据访问层时创建可重用和线程安全的对象。这种方式设计上类似于JDO查询、你可以定义查询字符串,声明参数及编译查询语句。一旦完成这些工作之后,执行方法可以根据不同的传入参数被多次调用。

    15.1.2 包层级
    Spring的JDBC框架一共包含4种不同类型的包、包括core,datasource,object和support.

    org.springframework.jdbc.core包含JdbcTemplate 类和它各种回调接口、外加一些相关的类。它的一个子包
    org.springframework.jdbc.core.simple包含SimpleJdbcInsert和SimpleJdbcCall等类。另一个叫org.springframework.jdbc.core.namedparam的子包包含NamedParameterJdbcTemplate及它的一些工具类。详见:
    15.2:“使用JDBC核心类控制基础的JDBC处理过程和异常处理机制”
    15.4:“JDBC批量操作”
    15.5:“利用SimpleJdbc 类简化JDBC操作”.

    org.springframework.jdbc.datasource包包含DataSource数据源访问的工具类,以及一些简单的DataSource实现用于测试和脱离JavaEE容器运行的JDBC代码。子包org.springfamework.jdbc.datasource.embedded提供Java内置数据库例如HSQL, H2, 和Derby的支持。详见:
    15.3:“控制数据库连接”
    15.8:“内置数据库支持”.

    org.springframework.jdbc.object包含用于在RDBMS查询、更新和存储过程中创建线程安全及可重用的对象类。详见15.6: “像Java对象那样操作JDBC”;这种方式类似于JDO的查询方式,不过查询返回的对象是与数据库脱离的。此包针对JDBC做了很多上层封装、而底层依赖于org.springframework.jdbc.core包。

    org.springframework.jdbc.support包含SQLException的转换类和一些工具类。JDBC处理过程中抛出的异常会被转换成org.springframework.dao里面定义的异常类。这意味着SpringJDBC抽象层的代码不需要实现JDBC或者RDBMS特定的错误处理方式。所有转换的异常都没有被捕获,而是让开发者自己处理异常、具体的话既可以捕获异常也可以直接抛给上层调用者
    详见:15.2.3:“SQL异常转换器”.

    15.2 使用JDBC核心类控制基础的JDBC处理过程和异常处理机制

    15.2.1 JdbcTemplate

    JdbcTemplate是JDBC core包里面的核心类。它封装了对资源的创建和释放,可以帮你避免忘记关闭连接等常见错误。它也包含了核心JDBC工作流的一些基础工作、例如执行和声明语句,而把SQL语句的生成以及查询结果的提取工作留给应用代码。JdbcTemplate执行查询、更新SQL语句和调用存储过程,运行结果集迭代和抽取返回参数值。它也可以捕获JDBC异常并把它们转换成更加通用、解释性更强的异常层次结构、这些异常都定义在org.springframework.dao包里面。

    当你在代码中使用了JdbcTemplate类,你只需要实现回调接口。PreparedStatementCreator回调接口通过传入的Connection类(该类包含SQL和任何必要的参数)创建已声明的语句。CallableStatementCreator也提供类似的方式、该接口用于创建回调语句。RowCallbackHandler用于获取结果集每一行的值。

    可以在DAO实现类中通过传入DataSource引用来完成JdbcTemplate的初始化;也可以在Spring IOC容器里面配置、作为DAO bean的依赖Bean配置。

    备注:DataSource最好在Spring IOC容器里作为Bean配置起来。在上面第一种情况下,DataSource bean直接传给相关的服务;第二种情况下DataSource bean传递给JdbcTemplate bean。

    JdbcTemplate中使用的所有SQL以“DEBUG”级别记入日志(一般情况下日志的归类是JdbcTemplate对应的全限定类名,不过如果需要对JdbcTemplate进行定制的话,可能是它的子类名)

    JdbcTemplate 使用示例

    这一节提供了JdbcTemplate类的一些使用例子。这些例子没有囊括JdbcTemplate可提供的所有功能;全部功能和用法请详见相关的javadocs.

    查询 (SELECT)

    下面是一个简单的例子、用于获取关系表里面的行数

    1 int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);

    使用绑定变量的简单查询:

    1 int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
    2         "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");

    String查询:

    1 String lastName = this.jdbcTemplate.queryForObject(
    2         "select last_name from t_actor where id = ?",
    3         new Object[]{1212L}, String.class);

    查询和填充领域模型:

    01 Actor actor = this.jdbcTemplate.queryForObject(
    02         "select first_name, last_name from t_actor where id = ?",
    03         new Object[]{1212L},
    04         new RowMapper() {
    05             public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
    06                 Actor actor = new Actor();
    07                 actor.setFirstName(rs.getString("first_name"));
    08                 actor.setLastName(rs.getString("last_name"));
    09                 return actor;
    10             }
    11         });

    查询和填充多个领域对象:

    01 List actors = this.jdbcTemplate.query(
    02         "select first_name, last_name from t_actor",
    03         new RowMapper() {
    04             public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
    05                 Actor actor = new Actor();
    06                 actor.setFirstName(rs.getString("first_name"));
    07                 actor.setLastName(rs.getString("last_name"));
    08                 return actor;
    09             }
    10         });

    如果上面的两段代码实际存在于相同的应用中,建议把RowMapper匿名类中重复的代码抽取到单独的类中(通常是一个静态类),方便被DAO方法引用。例如,上面的代码例子更好的写法如下:

    01 public List findAllActors() {
    02     return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
    03 }
    04  
    05 private static final class ActorMapper implements RowMapper {
    06  
    07     public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
    08         Actor actor = new Actor();
    09         actor.setFirstName(rs.getString("first_name"));
    10         actor.setLastName(rs.getString("last_name"));
    11         return actor;
    12     }
    13 }

    使用jdbcTemplate实现增删改

    你可以使用update(..)方法实现插入,更新和删除操作。参数值可以通过可变参数或者封装在对象内传入。

    1 this.jdbcTemplate.update(
    2         "insert into t_actor (first_name, last_name) values (?, ?)",
    3         "Leonor", "Watling");
    1 this.jdbcTemplate.update(
    2         "update t_actor set last_name = ? where id = ?",
    3         "Banjo", 5276L);
    1 this.jdbcTemplate.update(
    2         "delete from actor where id = ?",
    3         Long.valueOf(actorId));

    其他jdbcTemplate操作

    你可以使用execute(..)方法执行任何SQL,甚至是DDL语句。这个方法可以传入回调接口、绑定可变参数数组等。

    1 this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

    下面的例子调用一段简单的存储过程。更复杂的存储过程支持文档后面会有描述。

    1 this.jdbcTemplate.update(
    2         "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
    3         Long.valueOf(unionId));

    JdbcTemplate 最佳实践
    JdbcTemplate实例一旦配置之后是线程安全的。这点很重要因为这样你就能够配置JdbcTemplate的单例,然后安全的将其注入到多个DAO中(或者repositories)。JdbcTemplate是有状态的,内部存在对DataSource的引用,但是这种状态不是会话状态。

    使用JdbcTemplate类的常用做法是在你的Spring配置文件里配置好一个DataSource,然后将其依赖注入进你的DAO类中(NamedParameterJdbcTemplate也是如此)。JdbcTemplate在DataSource的Setter方法中被创建。就像如下DAO类的写法一样:

    01 public class JdbcCorporateEventDao implements CorporateEventDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         this.jdbcTemplate = new JdbcTemplate(dataSource);
    07     }
    08  
    09     // JDBC-backed implementations of the methods on the CorporateEventDao follow...
    10 }

    相关的配置是这样的:

    01 "1.0" encoding="UTF-8"?>
    02 "http://www.springframework.org/schema/beans"
    03     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    04     xmlns:context="http://www.springframework.org/schema/context"
    05     xsi:schemaLocation="
    06         http://www.springframework.org/schema/beans
    07         http://www.springframework.org/schema/beans/spring-beans.xsd
    08         http://www.springframework.org/schema/context
    09         http://www.springframework.org/schema/context/spring-context.xsd">
    10  
    11     "corporateEventDao" class="com.example.JdbcCorporateEventDao">
    12         "dataSource" ref="dataSource"/>
    13     
    14  
    15     "dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    16         "driverClassName" value="${jdbc.driverClassName}"/>
    17         "url" value="${jdbc.url}"/>
    18         "username" value="${jdbc.username}"/>
    19         "password" value="${jdbc.password}"/>
    20     
    21  
    22     "jdbc.properties"/>
    23  
    24

    另一种替代显式配置的方式是使用component-scanning和注解注入。在这个场景下需要添加@Repository注解(添加这个注解可以被component-scanning扫描到),同时在DataSource的Setter方法上添加@Autowired注解:

    01 @Repository
    02 public class JdbcCorporateEventDao implements CorporateEventDao {
    03  
    04     private JdbcTemplate jdbcTemplate;
    05  
    06     @Autowired
    07     public void setDataSource(DataSource dataSource) {
    08         this.jdbcTemplate = new JdbcTemplate(dataSource);
    09     }
    10  
    11     // JDBC-backed implementations of the methods on the CorporateEventDao follow...
    12 }

    相关的XML配置如下:

    01 "1.0" encoding="UTF-8"?>
    02 "http://www.springframework.org/schema/beans"
    03     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    04     xmlns:context="http://www.springframework.org/schema/context"
    05     xsi:schemaLocation="
    06         http://www.springframework.org/schema/beans
    07         http://www.springframework.org/schema/beans/spring-beans.xsd
    08         http://www.springframework.org/schema/context
    09         http://www.springframework.org/schema/context/spring-context.xsd">
    10  
    11     
    12     package="org.springframework.docs.test" />
    13  
    14     "dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    15         "driverClassName" value="${jdbc.driverClassName}"/>
    16         "url" value="${jdbc.url}"/>
    17         "username" value="${jdbc.username}"/>
    18         "password" value="${jdbc.password}"/>
    19     
    20  
    21     "jdbc.properties"/>
    22  
    23

    如果你使用Spring的JdbcDaoSupport类,许多JDBC相关的DAO类都从该类继承过来,这个时候相关子类需要继承JdbcDaoSupport类的setDataSource方法。当然你也可以选择不从这个类继承,JdbcDaoSupport本身只是提供一些便利性。

    无论你选择上面提到的哪种初始方式,当你在执行SQL语句时一般都不需要重新创建JdbcTemplate 实例。JdbcTemplate一旦被配置后其实例都是线程安全的。当你的应用需要访问多个数据库时你可能也需要多个JdbcTemplate实例,相应的也需要多个DataSources,同时对应多个JdbcTemplates配置。

    15.2.2 NamedParameterJdbcTemplate
    NamedParameterJdbcTemplate 提供对JDBC语句命名参数的支持,而普通的JDBC语句只能使用经典的 ‘?’参数。NamedParameterJdbcTemplate内部包装了JdbcTemplate,很多功能是直接通过JdbcTemplate来实现的。本节主要描述NamedParameterJdbcTemplate不同于JdbcTemplate 的点;即通过使用命名参数来操作JDBC

    01 // some JDBC-backed DAO class...
    02 private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    03  
    04 public void setDataSource(DataSource dataSource) {
    05     this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    06 }
    07  
    08 public int countOfActorsByFirstName(String firstName) {
    09  
    10     String sql = "select count(*) from T_ACTOR where first_name = :first_name";
    11  
    12     SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
    13  
    14     return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
    15 }

    上面代码块可以看到SQL变量中命名参数的标记用法,以及namedParameters变量的相关赋值(类型为MapSqlParameterSource)

    除此以外,你还可以在NamedParameterJdbcTemplate中传入Map风格的命名参数及相关的值。NamedParameterJdbcTemplate类从NamedParameterJdbcOperations接口实现的其他方法用法是类似的,这里就不一一叙述了。

    下面是一个Map风格的例子:

    01 // some JDBC-backed DAO class...
    02 private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    03  
    04 public void setDataSource(DataSource dataSource) {
    05     this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    06 }
    07  
    08 public int countOfActorsByFirstName(String firstName) {
    09  
    10     String sql = "select count(*) from T_ACTOR where first_name = :first_name";
    11  
    12     Map namedParameters = Collections.singletonMap("first_name", firstName);
    13  
    14     return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters,  Integer.class);
    15 }

    与NamedParameterJdbcTemplate相关联的SqlParameterSource接口提供了很有用的功能(两者在同一个包里面)。在上面的代码片段中你已经看到了这个接口的一个实现例子(就是MapSqlParameterSource类)。SqlParameterSource类是NamedParameterJdbcTemplate
    类的数值值来源。MapSqlParameterSource实现非常简单、只是适配了java.util.Map,其中Key就是参数名字,Value就是参数值。

    另外一个SqlParameterSource 的实现是BeanPropertySqlParameterSource类。这个类封装了任意一个JavaBean(也就是任意符合JavaBen规范的实例),在这个实现中,使用了JavaBean的属性作为命名参数的来源。

    01 public class Actor {
    02  
    03     private Long id;
    04     private String firstName;
    05     private String lastName;
    06  
    07     public String getFirstName() {
    08         return this.firstName;
    09     }
    10  
    11     public String getLastName() {
    12         return this.lastName;
    13     }
    14  
    15     public Long getId() {
    16         return this.id;
    17     }
    18  
    19     // setters omitted...
    20  
    21 }
    01 // some JDBC-backed DAO class...
    02 private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    03  
    04 public void setDataSource(DataSource dataSource) {
    05     this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    06 }
    07  
    08 public int countOfActors(Actor exampleActor) {
    09  
    10     // notice how the named parameters match the properties of the above 'Actor' class
    11     String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
    12  
    13     SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
    14  
    15     return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
    16 }

    之前提到过NamedParameterJdbcTemplate本身包装了经典的JdbcTemplate模板。如果你想调用只存在于JdbcTemplate类中的方法,你可以使用getJdbcOperations()方法、该方法返回JdbcOperations接口,通过这个接口你可以调用内部JdbcTemplate的方法。

    NamedParameterJdbcTemplate 类在应用上下文的使用方式也可见:“JdbcTemplate最佳实践”

    15.2.3 SQLExceptionTranslator

    SQLExceptionTranslator接口用于在SQLExceptions和spring自己的org.springframework.dao.DataAccessException之间做转换,要处理批量更新或者从文件中这是为了屏蔽底层的数据访问策略。其实现可以是比较通用的(例如,使用JDBC的SQLState编码),或者是更精确专有的(例如,使用Oracle的错误类型编码)

    SQLExceptionTranslator 接口的默认实现是SQLErrorCodeSQLExceptionTranslator,该实现使用的是指定数据库厂商的错误编码,因为要比SQLState的实现更加精确。错误码转换过程基于JavaBean类型的SQLErrorCodes。这个类通过SQLErrorCodesFactory创建和返回,SQLErrorCodesFactory是一个基于sql-error-codes.xml配置内容来创建SQLErrorCodes的工厂类。该配置中的数据库厂商代码基于Database MetaData信息中返回的数据库产品名(DatabaseProductName),最终使用的就是你正在使用的实际数据库中错误码。

    SQLErrorCodeSQLExceptionTranslator按以下的顺序来匹配规则:

    备注:SQLErrorCodesFactory是用于定义错误码和自定义异常转换的缺省工厂类。错误码参照Classpath下配置的sql-error-codes.xml文件内容,相匹配的SQLErrorCodes实例基于正在使用的底层数据库的元数据名称

  • 是否存在自定义转换的子类。通常直接使用SQLErrorCodeSQLExceptionTranslator就可以了,因此此规则一般不会生效。只有你真正自己实现了一个子类才会生效。
  • 是否存在SQLExceptionTranslator接口的自定义实现,通过SQLErrorCodes类的customSqlExceptionTranslator属性指定
  • SQLErrorCodes的customTranslations属性数组、类型为CustomSQLErrorCodesTranslation类实例列表、能否被匹配到
  • 错误码被匹配到
  • 使用兜底的转换器。SQLExceptionSubclassTranslator是缺省的兜底转换器。如果此转换器也不存在的话只能使用SQLStateSQLExceptionTranslator

    你可以继承SQLErrorCodeSQLExceptionTranslator:

    1 public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
    2  
    3     protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
    4         if (sqlex.getErrorCode() == -12345) {
    5             return new DeadlockLoserDataAccessException(task, sqlex);
    6         }
    7         return null;
    8     }
    9 }

    这个例子中,特定的错误码-12345被识别后单独转换,而其他的错误码则通过默认的转换器实现来处理。在使用自定义转换器时,有必要通过setExceptionTranslator方法传入JdbcTemplate ,并且使用JdbcTemplate来做所有的数据访问处理。下面是一个如何使用自定义转换器的例子

    01 private JdbcTemplate jdbcTemplate;
    02  
    03 public void setDataSource(DataSource dataSource) {
    04  
    05     // create a JdbcTemplate and set data source
    06     this.jdbcTemplate = new JdbcTemplate();
    07     this.jdbcTemplate.setDataSource(dataSource);
    08  
    09     // create a custom translator and set the DataSource for the default translation lookup
    10     CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
    11     tr.setDataSource(dataSource);
    12     this.jdbcTemplate.setExceptionTranslator(tr);
    13  
    14 }
    15  
    16 public void updateShippingCharge(long orderId, long pct) {
    17     // use the prepared JdbcTemplate for this update
    18     this.jdbcTemplate.update("update orders" +
    19         " set shipping_charge = shipping_charge * ? / 100" +
    20         " where id = ?", pct, orderId);
    21 }

    自定义转换器需要传入dataSource对象为了能够获取sql-error-codes.xml定义的错误码

    15.2.4 执行SQL语句

    执行一条SQL语句非常方便。你只需要依赖DataSource和JdbcTemplate,包括JdbcTemplate提供的工具方法。
    下面的例子展示了如何创建一个新的数据表,虽然只有几行代码、但已经完全可用了:

    01 import javax.sql.DataSource;
    02 import org.springframework.jdbc.core.JdbcTemplate;
    03  
    04 public class ExecuteAStatement {
    05  
    06     private JdbcTemplate jdbcTemplate;
    07  
    08     public void setDataSource(DataSource dataSource) {
    09         this.jdbcTemplate = new JdbcTemplate(dataSource);
    10     }
    11  
    12     public void doExecute() {
    13         this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
    14     }
    15 }

    15.2.5 查询
    一些查询方法会返回一个单一的结果。使用queryForObject(..)返回结果计数或特定值。当返回特定值类型时,将Java类型作为方法参数传入、最终返回的JDBC类型会被转换成相应的Java类型。如果这个过程中间出现类型转换错误,则会抛出InvalidDataAccessApiUsageException的异常。下面的例子包含两个查询方法,一个返回int类型、另一个返回了String类型。

    01 import javax.sql.DataSource;
    02 import org.springframework.jdbc.core.JdbcTemplate;
    03  
    04 public class RunAQuery {
    05  
    06     private JdbcTemplate jdbcTemplate;
    07  
    08     public void setDataSource(DataSource dataSource) {
    09         this.jdbcTemplate = new JdbcTemplate(dataSource);
    10     }
    11  
    12     public int getCount() {
    13         return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
    14     }
    15  
    16     public String getName() {
    17         return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
    18     }
    19 }

    除了返回单一查询结果的方法外,其他方法返回一个列表、列表中每一项代表查询返回的行记录。其中最通用的方式是queryForList(..),返回一个列表,列表每一项是一个Map类型,包含数据库对应行每一列的具体值。下面的代码块给上面的例子添加一个返回所有行的方法:

    1 private JdbcTemplate jdbcTemplate;
    2  
    3 public void setDataSource(DataSource dataSource) {
    4     this.jdbcTemplate = new JdbcTemplate(dataSource);
    5 }
    6  
    7 public List> getList() {
    8     return this.jdbcTemplate.queryForList("select * from mytable");
    9 }

    返回的列表结果数据格式是这样的:

    1 [{name=Bob, id=1}, {name=Mary, id=2}]

    15.2.6 更新数据库

    下面的例子根据主键更新其中一列值。在这个例子中,一条SQL语句包含行参数的占位符。参数值可以通过可变参数或者对象数组传入。元数据类型需要显式或者自动装箱成对应的包装类型

    01 import javax.sql.DataSource;
    02  
    03 import org.springframework.jdbc.core.JdbcTemplate;
    04  
    05 public class ExecuteAnUpdate {
    06  
    07     private JdbcTemplate jdbcTemplate;
    08  
    09     public void setDataSource(DataSource dataSource) {
    10         this.jdbcTemplate = new JdbcTemplate(dataSource);
    11     }
    12  
    13     public void setName(int id, String name) {
    14         this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
    15     }
    16 }

    15.2.7 获取自增Key
    update()方法支持获取数据库自增Key。这个支持已成为JDBC3.0标准之一、更多细节详见13.6章。这个方法使用PreparedStatementCreator作为其第一个入参,该类可以指定所需的insert语句。另外一个参数是KeyHolder,包含了更新操作成功之后产生的自增Key。这不是标准的创建PreparedStatement 的方式。下面的例子可以在Oracle上面运行,但在其他平台上可能就不行了。

    01 final String INSERT_SQL = "insert into my_test (name) values(?)";
    02 final String name = "Rob";
    03  
    04 KeyHolder keyHolder = new GeneratedKeyHolder();
    05 jdbcTemplate.update(
    06     new PreparedStatementCreator() {
    07         public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
    08             PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] {"id"});
    09             ps.setString(1, name);
    10             return ps;
    11         }
    12     },
    13     keyHolder);
    14  
    15 // keyHolder.getKey() now contains the generated key

    15.3 控制数据库连接
    15.3.1 DataSource

    Spring用DataSource来保持与数据库的连接。DataSource是JDBC规范的一部分同时是一种通用的连接工厂。它使得框架或者容器对应用代码屏蔽连接池或者事务管理等底层逻辑。作为开发者,你无需知道连接数据库的底层逻辑;这只是创建datasource的管理员该负责的模块。在开发测试过程中你可能需要同时扮演双重角色,但最终上线时你不需要知道生产数据源是如何配置的。

    当使用Spring JDBC时,你可以通过JNDI获取数据库数据源、也可以利用第三方依赖包的连接池实现来配置。比较受欢迎的三方库有Apache Jakarta Commons DBCP 和 C3P0。在Spring产品内,有自己的数据源连接实现,但仅仅用于测试目的,同时并没有使用到连接池。

    这一节使用了Spring的DriverManagerDataSource实现、其他更多的实现会在后面提到。

    注意:仅仅使用DriverManagerDataSource类只是为了测试目的、因为此类没有连接池功能,因此在并发连接请求时性能会比较差

    通过DriverManagerDataSource获取数据库连接的方式和传统JDBC是类似的。首先指定JDBC驱动的类全名,DriverManager 会据此来加载驱动类。接下来、提供JDBC驱动对应的URL名称。(可以从相应驱动的文档里找到具体的名称)。然后传入用户名和密码来连接数据库。下面是一个具体配置DriverManagerDataSource连接的Java代码块:

    1 DriverManagerDataSource dataSource = new DriverManagerDataSource();
    2 dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
    3 dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
    4 dataSource.setUsername("sa");
    5 dataSource.setPassword("");

    接下来是相关的XML配置:

    1 "dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    2     "driverClassName" value="${jdbc.driverClassName}"/>
    3     "url" value="${jdbc.url}"/>
    4     "username" value="${jdbc.username}"/>
    5     "password" value="${jdbc.password}"/>
    6
    7  
    8 "jdbc.properties"/>

    下面的例子展示的是DBCP和C3P0的基础连接配置。如果需要连接更多的连接池选项、请查看各自连接池实现的具体产品文档

    DBCP配置:

    1 "dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    2     "driverClassName" value="${jdbc.driverClassName}"/>
    3     "url" value="${jdbc.url}"/>
    4     "username" value="${jdbc.username}"/>
    5     "password" value="${jdbc.password}"/>
    6
    7  
    8 "jdbc.properties"/>

    C3P0配置:

    1 "dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    2     "driverClass" value="${jdbc.driverClassName}"/>
    3     "jdbcUrl" value="${jdbc.url}"/>
    4     "user" value="${jdbc.username}"/>
    5     "password" value="${jdbc.password}"/>
    6
    7  
    8 "jdbc.properties"/>

    15.3.2 DataSourceUtils
    DataSourceUtils类是一个方便有用的工具类,提供了从JNDI获取和关闭连接等有用的静态方法。它支持线程绑定的连接、例如:使用DataSourceTransactionManager的时候,将把数据库连接绑定到当前的线程上。

    15.3.3 SmartDataSource
    实现SmartDataSource接口的实现类需要能够提供到关系数据库的连接。它继承了DataSource接口,允许使用它的类查询是否在某个特定的操作后需要关闭连接。这在当你需要重用连接时比较有用。

    15.3.4 AbstractDataSource
    AbstractDataSource是Spring DataSource实现的基础抽象类,封装了DataSource的基础通用功能。你可以继承AbstractDataSource自定义DataSource 实现。

    15.3.5 SingleConnectionDataSource
    SingleConnectionDataSource实现了SmartDataSource接口、内部封装了一个在每次使用后都不会关闭的单一连接。显然,这种场景下无法支持多线程。

    为了防止客户端代码误以为数据库连接来自连接池(就像使用持久化工具时一样)错误的调用close方法,你应将suppressClose设置为true。这样,通过该类获取的将是代理连接(禁止关闭)而不是原有的物理连接。需要注意你不能将这个类强制转换成Oracle等数据库的原生连接。

    这个类主要用于测试目的。例如,他使得测试代码能够脱离应用服务器,很方便的在单一的JNDI环境下调试。和DriverManagerDataSource相反,它总是重用相同的连接,这是为了避免在测试过程中创建过多的物理连接。

    15.3.6 DriverManagerDataSource
    DriverManagerDataSource类实现了标准的DataSource接口,可以通过Java Bean属性来配置原生的JDBC驱动,并且每次都返回一个新的连接。

    这个实现对于测试和JavaEE容器以外的独立环境比较有用,无论是作为一个在Spring IOC容器内的DataSource Bean,或是在单一的JNDI环境中。由于Connection.close()仅仅只是简单的关闭数据库连接,因此任何能够操作DataSource的持久层代码都能很好的工作。但是,使用JavaBean类型的连接池,比如commons-dbcp往往更简单、即使是在测试环境下也是如此,因此更推荐commons-dbcp。

    15.3.7 TransactionAwareDataSourceProxy

    TransactionAwareDataSourceProxy会创建一个目标DataSource的代理,内部包装了DataSource,在此基础上添加了Spring事务管理功能。有点类似于JavaEE服务器中提供的JNDI事务数据源。

    注意:一般情况下很少用到这个类,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource接口实现作为参数。在这种场景下,使用proxy可以仍旧重用老代码,同时能够有Spring管理事务的能力。更多的场景下更推荐使用JdbcTemplate和DataSourceUtils等更高抽象的资源管理类.

    (更多细节请查看TransactionAwareDataSourceProxy的JavaDoc)
    15.3.8 DataSourceTransactionManager
    DataSourceTransactionManager类实现了PlatformTransactionManager接口。它将JDBC连接从指定的数据源绑定到当前执行的线程中,
    允许一个线程连接对应一个数据源。

    应用代码需要通过DataSourceUtils.getConnection(DataSource) 来获取JDBC连接,而不是通过JavaEE标准的DataSource.getConnection来获取。它会抛出org.springframework.dao的运行时异常而不是编译时SQL异常。所有框架类像JdbcTemplate都默认使用这个策略。如果不需要和这个 DataSourceTransactionManager类一起使用,DataSourceUtils 提供的功能跟一般的数据库连接策略没有什么两样,因此它可以在任何场景下使用。

    DataSourceTransactionManager支持自定义隔离级别,以及JDBC查询超时机制。为了支持后者,应用代码必须在每个创建的语句中使用JdbcTemplate或是调用DataSourceUtils.applyTransactionTimeout(..)方法

    在单一的资源使用场景下它可以替代JtaTransactionManager,不需要要求容器去支持JTA。如果你严格遵循连接查找的模式的话、可以通过配置来做彼此切换。JTA本身不支持自定义隔离级别!

    15.4 JDBC批量操作
    大多数JDBC驱动在针对同一SQL语句做批处理时能够获得更好的性能。批量更新操作可以节省数据库的来回传输次数。

    15.4.1 使用JdbcTemplate来进行基础的批量操作
    通过JdbcTemplate 实现批处理需要实现特定接口的两个方法,BatchPreparedStatementSetter,并且将其作为第二个参数传入到batchUpdate方法调用中。使用getBatchSize提供当前批量操作的大小。使用setValues方法设置语句的Value参数。这个方法会按getBatchSize设置中指定的调用次数。下面的例子中通过传入列表来批量更新actor表。在这个例子中整个列表使用了批量操作:

    01 public class JdbcActorDao implements ActorDao {
    02     private JdbcTemplate jdbcTemplate;
    03  
    04     public void setDataSource(DataSource dataSource) {
    05         this.jdbcTemplate = new JdbcTemplate(dataSource);
    06     }
    07  
    08     public int[] batchUpdate(final List actors) {
    09         int[] updateCounts = jdbcTemplate.batchUpdate("update t_actor set first_name = ?, " +
    10                 "last_name = ? where id = ?",
    11             new BatchPreparedStatementSetter() {
    12                 public void setValues(PreparedStatement ps, int i) throws SQLException {
    13                         ps.setString(1, actors.get(i).getFirstName());
    14                         ps.setString(2, actors.get(i).getLastName());
    15                         ps.setLong(3, actors.get(i).getId().longValue());
    16                     }
    17  
    18                     public int getBatchSize() {
    19                         return actors.size();
    20                     }
    21                 });
    22         return updateCounts;
    23     }
    24  
    25     // ... additional methods
    26 }

    如果你需要处理批量更新或者从文件中批量读取,你可能需要确定一个合适的批处理大小,但是最后一次批处理可能达不到这个大小。在这种场景下你可以使用InterruptibleBatchPreparedStatementSetter接口,允许在输入流耗尽之后终止批处理,isBatchExhausted方法使得你可以指定批处理结束时间。

    15.4.2 对象列表的批量处理
    JdbcTemplate和NamedParameterJdbcTemplate都提供了批量更新的替代方案。这个时候不是实现一个特定的批量接口,而是在调用时传入所有的值列表。框架会循环访问这些值并且使用内部的SQL语句setter方法。你是否已声明参数对应API是不一样的。针对已声明参数你需要传入qlParameterSource数组,每项对应单次的批量操作。你可以使用SqlParameterSource.createBatch方法来创建这个数组,传入JavaBean数组或是包含参数值的Map数组。

    下面是一个使用已声明参数的批量更新例子:

    01 public class JdbcActorDao implements ActorDao {
    02     private NamedParameterTemplate namedParameterJdbcTemplate;
    03  
    04     public void setDataSource(DataSource dataSource) {
    05         this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    06     }
    07  
    08     public int[] batchUpdate(final List actors) {
    09         SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
    10         int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
    11                 "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
    12                 batch);
    13         return updateCounts;
    14     }
    15  
    16     // ... additional methods
    17 }

    对于使用“?”占位符的SQL语句,你需要传入带有更新值的对象数组。对象数组每一项对应SQL语句中的一个占位符,并且传入顺序需要和SQL语句中定义的顺序保持一致。

    下面是使用经典JDBC“?”占位符的例子:

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         this.jdbcTemplate = new JdbcTemplate(dataSource);
    07     }
    08  
    09     public int[] batchUpdate(final List actors) {
    10         List batch = new ArrayList();
    11         for (Actor actor : actors) {
    12             Object[] values = new Object[] {
    13                     actor.getFirstName(),
    14                     actor.getLastName(),
    15                     actor.getId()};
    16             batch.add(values);
    17         }
    18         int[] updateCounts = jdbcTemplate.batchUpdate(
    19                 "update t_actor set first_name = ?, last_name = ? where id = ?",
    20                 batch);
    21         return updateCounts;
    22     }
    23  
    24     // ... additional methods
    25  
    26 }

    上面所有的批量更新方法都返回一个数组,包含具体成功的行数。这个计数是由JDBC驱动返回的。如果拿不到计数。JDBC驱动会返回-2。

    15.4.3 多个批处理操作
    上面最后一个例子更新的批处理数量太大,最好能再分割成更小的块。最简单的方式就是你多次调用batchUpdate来实现,但是可以有更优的方法。要使用这个方法除了SQL语句,还需要传入参数集合对象,每次Batch的更新数和一个ParameterizedPreparedStatementSetter去设置预编译SQL语句的参数值。框架会循环调用提供的值并且将更新操作切割成指定数量的小批次。

    下面的例子设置了更新批次数量为100的批量更新操作:

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         this.jdbcTemplate = new JdbcTemplate(dataSource);
    07     }
    08  
    09     public int[][] batchUpdate(final Collection actors) {
    10         int[][] updateCounts = jdbcTemplate.batchUpdate(
    11                 "update t_actor set first_name = ?, last_name = ? where id = ?",
    12                 actors,
    13                 100,
    14                 new ParameterizedPreparedStatementSetter() {
    15                     public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
    16                         ps.setString(1, argument.getFirstName());
    17                         ps.setString(2, argument.getLastName());
    18                         ps.setLong(3, argument.getId().longValue());
    19                     }
    20                 });
    21         return updateCounts;
    22     }
    23  
    24     // ... additional methods
    25  
    26 }

    这个调用的批量更新方法返回一个包含int数组的二维数组,包含每次更新生效的行数。第一层数组长度代表批处理执行的数量,第二层数组长度代表每个批处理生效的更新数。每个批处理的更新数必须和所有批处理的大小匹配,除非是最后一次批处理可能小于这个数,具体依赖于更新对象的总数。每次更新语句生效的更新数由JDBC驱动提供。如果更新数量不存在,JDBC驱动会返回-2

    15.5 利用SimpleJdbc类简化JDBC操作

    SimpleJdbcInsert类和SimpleJdbcCall类主要利用了JDBC驱动所提供的数据库元数据的一些特性来简化数据库操作配置。这意味着可以在前端减少配置,当然你也可以覆盖或是关闭底层的元数据处理,在代码里面指定所有的细节。

    15.5.1 利用SimpleJdbcInsert插入数据
    让我们首先看SimpleJdbcInsert类可提供的最小配置选项。你需要在数据访问层初始化方法里面初始化SimpleJdbcInsert类。在这个例子中,初始化方法是setDataSource。你不需要继承SimpleJdbcInsert,只需要简单的创建其实例同时调用withTableName设置数据库名。

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcInsert insertActor;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
    09     }
    10  
    11     public void add(Actor actor) {
    12         Map parameters = new HashMap(3);
    13         parameters.put("id", actor.getId());
    14         parameters.put("first_name", actor.getFirstName());
    15         parameters.put("last_name", actor.getLastName());
    16         insertActor.execute(parameters);
    17     }
    18  
    19     // ... additional methods
    20 }

    代码中的execute只传入java.utils.Map作为唯一参数。需要注意的是Map里面用到的Key必须和数据库中表对应的列名一一匹配。这是因为我们需要按顺序读取元数据来构造实际的插入语句。

    15.5.2 使用SimpleJdbcInsert获取自增Key
    接下来,我们对于同样的插入语句,我们并不传入id,而是通过数据库自动获取主键的方式来创建新的Actor对象并插入数据库。 当我们创建SimpleJdbcInsert实例时, 我们不仅需要指定表名,同时我们通过usingGeneratedKeyColumns方法指定需要数据库自动生成主键的列名。

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcInsert insertActor;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         this.insertActor = new SimpleJdbcInsert(dataSource)
    09                 .withTableName("t_actor")
    10                 .usingGeneratedKeyColumns("id");
    11     }
    12  
    13     public void add(Actor actor) {
    14         Map parameters = new HashMap(2);
    15         parameters.put("first_name", actor.getFirstName());
    16         parameters.put("last_name", actor.getLastName());
    17         Number newId = insertActor.executeAndReturnKey(parameters);
    18         actor.setId(newId.longValue());
    19     }
    20  
    21     // ... additional methods
    22 }

    执行插入操作时第二种方式最大的区别是你不是在Map中指定ID,而是调用executeAndReturnKey方法。这个方法返回java.lang.Number对象,可以创建一个数值类型的实例用于我们的领域模型中。你不能仅仅依赖所有的数据库都返回一个指定的Java类;java.lang.Number是你可以依赖的基础类。如果你有多个自增列,或者自增的值是非数值型的,你可以使用executeAndReturnKeyHolder 方法返回的KeyHolder

    15.5.3 使用SimpleJdbcInsert指定列
    你可以在插入操作中使用usingColumns方法来指定特定的列名

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcInsert insertActor;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         this.insertActor = new SimpleJdbcInsert(dataSource)
    09                 .withTableName("t_actor")
    10                 .usingColumns("first_name", "last_name")
    11                 .usingGeneratedKeyColumns("id");
    12     }
    13  
    14     public void add(Actor actor) {
    15         Map parameters = new HashMap(2);
    16         parameters.put("first_name", actor.getFirstName());
    17         parameters.put("last_name", actor.getLastName());
    18         Number newId = insertActor.executeAndReturnKey(parameters);
    19         actor.setId(newId.longValue());
    20     }
    21  
    22     // ... additional methods
    23 }

    这里插入操作的执行和你依赖元数据决定更新哪个列的方式是一样的。

    15.5.4 使用SqlParameterSource 提供参数值
    使用Map来指定参数值没有问题,但不是最便捷的方法。Spring提供了一些SqlParameterSource接口的实现类来更方便的做这些操作。
    第一个是BeanPropertySqlParameterSource,如果你有一个JavaBean兼容的类包含具体的值,使用这个类是很方便的。他会使用相关的Getter方法来获取参数值。下面是一个例子:

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcInsert insertActor;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         this.insertActor = new SimpleJdbcInsert(dataSource)
    09                 .withTableName("t_actor")
    10                 .usingGeneratedKeyColumns("id");
    11     }
    12  
    13     public void add(Actor actor) {
    14         SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
    15         Number newId = insertActor.executeAndReturnKey(parameters);
    16         actor.setId(newId.longValue());
    17     }
    18  
    19     // ... additional methods
    20  
    21 }

    另外一个选择是使用MapSqlParameterSource,类似于Map、但是提供了一个更便捷的addValue方法可以用来做链式操作。

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcInsert insertActor;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         this.insertActor = new SimpleJdbcInsert(dataSource)
    09                 .withTableName("t_actor")
    10                 .usingGeneratedKeyColumns("id");
    11     }
    12  
    13     public void add(Actor actor) {
    14         SqlParameterSource parameters = new MapSqlParameterSource()
    15                 .addValue("first_name", actor.getFirstName())
    16                 .addValue("last_name", actor.getLastName());
    17         Number newId = insertActor.executeAndReturnKey(parameters);
    18         actor.setId(newId.longValue());
    19     }
    20  
    21     // ... additional methods
    22  
    23 }

    上面这些例子可以看出、配置是一样的,区别只是切换了不同的提供参数的实现方式来执行调用。
    15.5.5 利用SimpleJdbcCall调用存储过程

    SimpleJdbcCall利用数据库元数据的特性来查找传入的参数和返回值,这样你就不需要显式去定义他们。如果你喜欢的话也以自己定义参数,尤其对于某些参数,你无法直接将他们映射到Java类上,例如ARRAY类型和STRUCT类型的参数。下面第一个例子展示了一个存储过程,从一个MySQL数据库返回Varchar和Date类型。这个存储过程例子从指定的actor记录中查询返回first_name,last_name,和birth_date列。

    01 CREATE PROCEDURE read_actor (
    02     IN in_id INTEGER,
    03     OUT out_first_name VARCHAR(100),
    04     OUT out_last_name VARCHAR(100),
    05     OUT out_birth_date DATE)
    06 BEGIN
    07     SELECT first_name, last_name, birth_date
    08     INTO out_first_name, out_last_name, out_birth_date
    09     FROM t_actor where id = in_id;
    10 END;

    in_id 参数包含你正在查找的actor记录的id.out参数返回从数据库表读取的数据

    SimpleJdbcCall 和SimpleJdbcInsert定义的方式比较类似。你需要在数据访问层的初始化代码中初始化和配置该类。相比StoredProcedure类,你不需要创建一个子类并且不需要定义能够在数据库元数据中查找到的参数。下面是一个使用上面存储过程的SimpleJdbcCall配置例子。除了DataSource以外唯一的配置选项是存储过程的名字

    01 public class JdbcActorDao implements ActorDao {
    02     private JdbcTemplate jdbcTemplate;
    03     private SimpleJdbcCall procReadActor;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         this.jdbcTemplate = new JdbcTemplate(dataSource);
    07         this.procReadActor = new SimpleJdbcCall(dataSource)
    08                 .withProcedureName("read_actor");
    09     }
    10  
    11     public Actor readActor(Long id) {
    12         SqlParameterSource in = new MapSqlParameterSource()
    13                 .addValue("in_id", id);
    14         Map out = procReadActor.execute(in);
    15         Actor actor = new Actor();
    16         actor.setId(id);
    17         actor.setFirstName((String) out.get("out_first_name"));
    18         actor.setLastName((String) out.get("out_last_name"));
    19         actor.setBirthDate((Date) out.get("out_birth_date"));
    20         return actor;
    21     }
    22  
    23     // ... additional methods
    24  
    25 }

    调用代码包括创建包含传入参数的SqlParameterSource。这里需要重视的是传入参数值名字需要和存储过程中定义的参数名称相匹配。有一种场景不需要匹配、那就是你使用元数据去确定数据库对象如何与存储过程相关联。在存储过程源代码中指定的并不一定是数据库中存储的格式。有些数据库会把名字转成大写、而另外一些会使用小写或者特定的格式。

    execute方法接受传入参数,同时返回一个Map包含任意的返回参数,Map的Key是存储过程中指定的名字。在这个例子中它们是out_first_name, out_last_name 和 out_birth_date

    execute 方法的最后一部分使用返回的数据创建Actor对象实例。再次需要强调的是Out参数的名字必须是存储过程中定义的。结果Map中存储的返回参数名必须和数据库中的返回参数名(不同的数据库可能会不一样)相匹配,为了提高你代码的可重用性,你需要在查找中区分大小写,或者使用Spring里面的LinkedCaseInsensitiveMap。如果使用LinkedCaseInsensitiveMap,你需要创建自己的JdbcTemplate并且将setResultsMapCaseInsensitive属性设置为True。然后你将自定义的JdbcTemplate 传入到SimpleJdbcCall的构造器中。下面是这种配置的一个例子:

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private SimpleJdbcCall procReadActor;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    07         jdbcTemplate.setResultsMapCaseInsensitive(true);
    08         this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
    09                 .withProcedureName("read_actor");
    10     }
    11  
    12     // ... additional methods
    13  
    14 }

    通过这样的配置,你就可以无需担心返回参数值的大小写问题。

    15.5.6 为SimpleJdbcCall显式定义参数

    你已经了解如何通过元数据来简化参数配置,但如果你需要的话也可以显式指定参数。这样做的方法是在创建SimpleJdbcCall类同时通过declareParameters方法进行配置,这个方式可以传入一系列的SqlParameter。下面的章节会详细描述如何定义一个SqlParameter

    备注:如果你使用的数据库不是Spring支持的数据库类型的话显式定义就很有必要了。当前Spring支持以下数据库的存储过程元数据查找能力:Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle, 和 Sybase. 我们同时对某些数据库内置函数支持元数据特性:比如:MySQL、Microsoft SQL Server和Oracle。

    你可以选择显式定义一个、多个,或者所有参数。当你没有显式定义参数时元数据参数仍然会被使用。当你不想用元数据查找参数功能、只想指定参数时,需要调用withoutProcedureColumnMetaDataAccess方法。假设你针对同一个数据函数定义了两个或多个不同的调用方法签名,在每一个给定的签名中你需要使用useInParameterNames来指定传入参数的名称列表。下面是一个完全自定义的存储过程调用例子

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private SimpleJdbcCall procReadActor;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    07         jdbcTemplate.setResultsMapCaseInsensitive(true);
    08         this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
    09                 .withProcedureName("read_actor")
    10                 .withoutProcedureColumnMetaDataAccess()
    11                 .useInParameterNames("in_id")
    12                 .declareParameters(
    13                         new SqlParameter("in_id", Types.NUMERIC),
    14                         new SqlOutParameter("out_first_name", Types.VARCHAR),
    15                         new SqlOutParameter("out_last_name", Types.VARCHAR),
    16                         new SqlOutParameter("out_birth_date", Types.DATE)
    17                 );
    18     }
    19  
    20     // ... additional methods
    21 }

    两个例子的执行结果是一样的,区别是这个例子显式指定了所有细节,而不是仅仅依赖于数据库元数据。
    15.5.7 如何定义SqlParameters

    如何定义SimpleJdbc类和RDBMS操作类的参数,详见15.6: “像Java对象那样操作JDBC”,
    你需要使用SqlParameter或者是它的子类。通常需要在构造器中定义参数名和SQL类型。SQL类型使用java.sql.Types常量来定义。
    我们已经看到过类似于如下的定义:

    1 new SqlParameter("in_id", Types.NUMERIC),
    2     new SqlOutParameter("out_first_name", Types.VARCHAR),

    上面第一行SqlParameter 定义了一个传入参数。IN参数可以同时在存储过程调用和SqlQuery查询中使用,它的子类在下面的章节也有覆盖。

    上面第二行SqlOutParameter定义了在一次存储过程调用中使用的返回参数。还有一个SqlInOutParameter类,可以用于输入输出参数。也就是说,它既是一个传入参数,也是一个返回值。

    备注:参数只有被定义成SqlParameter和SqlInOutParameter才可以提供输入值。不像StoredProcedure类为了考虑向后兼容允许定义为SqlOutParameter的参数可以提供输入值

    对于输入参数,除了名字和SQL类型,你可以定义数值区间或是自定义数据类型名。针对输出参数,你可以使用RowMapper处理从REF游标返回的行映射。另外一种选择是定义SqlReturnType,可以针对返回值作自定义处理。

    15.5.8 使用SimpleJdbcCall调用内置存储函数

    调用存储函数几乎和调用存储过程的方式是一样的,唯一的区别你提供的是函数名而不是存储过程名。你可以使用withFunctionName方法作为配置的一部分表示我们想要调用一个函数,以及生成函数调用相关的字符串。一个特殊的execute调用,executeFunction,用来指定这个函数并且返回一个指定类型的函数值,这意味着你不需要从结果Map获取返回值。存储过程也有一个名字为executeObject的便捷方法,但是只要一个输出参数。下面的例子基于一个名字为get_actor_name的存储函数,返回actor的全名。下面是这个函数的Mysql源代码:

    1 CREATE FUNCTION get_actor_name (in_id INTEGER)
    2 RETURNS VARCHAR(200) READS SQL DATA
    3 BEGIN
    4     DECLARE out_name VARCHAR(200);
    5     SELECT concat(first_name, ' ', last_name)
    6         INTO out_name
    7         FROM t_actor where id = in_id;
    8     RETURN out_name;
    9 END;

    我们需要在初始方法中创建SimpleJdbcCall来调用这个函数

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private JdbcTemplate jdbcTemplate;
    04     private SimpleJdbcCall funcGetActorName;
    05  
    06     public void setDataSource(DataSource dataSource) {
    07         this.jdbcTemplate = new JdbcTemplate(dataSource);
    08         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    09         jdbcTemplate.setResultsMapCaseInsensitive(true);
    10         this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
    11                 .withFunctionName("get_actor_name");
    12     }
    13  
    14     public String getActorName(Long id) {
    15         SqlParameterSource in = new MapSqlParameterSource()
    16                 .addValue("in_id", id);
    17         String name = funcGetActorName.executeFunction(String.class, in);
    18         return name;
    19     }
    20  
    21     // ... additional methods
    22  
    23 }

    execute方法返回一个包含函数调用返回值的字符串

    15.5.9 从SimpleJdbcCall返回ResultSet/REF游标

    调用存储过程或者函数返回结果集会相对棘手一点。一些数据库会在JDBC结果处理中返回结果集,而另外一些数据库则需要明确指定返回值的类型。两种方式都需要循环迭代结果集做额外处理。通过SimpleJdbcCall,你可以使用returningResultSet方法,并定义一个RowMapper的实现类来处理特定的返回值。 当结果集在返回结果处理过程中没有被定义名称时,返回的结果集必须与定义的RowMapper的实现类指定的顺序保持一致。 而指定的名字也会被用作返回结果集中的名称。

    下面的例子使用了一个不包含输入参数的存储过程并且返回t_actor标的所有行。下面是这个存储过程的Mysql源代码:

    1 CREATE PROCEDURE read_all_actors()
    2 BEGIN
    3  SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
    4 END;

    调用这个存储过程你需要定义RowMapper。因为我们定义的Map类遵循JavaBean规范,所以我们可以使用BeanPropertyRowMapper作为实现类。 通过将相应的class类作为参数传入到newInstance方法中,我们可以创建这个实现类。

    01 public class JdbcActorDao implements ActorDao {
    02  
    03     private SimpleJdbcCall procReadAllActors;
    04  
    05     public void setDataSource(DataSource dataSource) {
    06         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
    07         jdbcTemplate.setResultsMapCaseInsensitive(true);
    08         this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
    09                 .withProcedureName("read_all_actors")
    10                 .returningResultSet("actors",
    11                 BeanPropertyRowMapper.newInstance(Actor.class));
    12     }
    13  
    14     public List getActorsList() {
    15         Map m = procReadAllActors.execute(new HashMap(0));
    16         return (List) m.get("actors");
    17     }
    18  
    19     // ... additional methods
    20  
    21 }

    execute调用传入一个空Map,因为这里不需要传入任何参数。从结果Map中提取Actors列表,并且返回给调用者。

    15.6 像Java对象那样操作JDBC

    org.springframework.jdbc.object包能让你更加面向对象化的访问数据库。举个例子,用户可以执行查询并返回一个list, 该list作为一个结果集将把从数据库中取出的列数据映射到业务对象的属性上。你也可以执行存储过程,包括更新、删除、插入语句。

    备注:许多Spring的开发者认为下面将描述的各种RDBMS操作类(StoredProcedure类除外)可以直接被JdbcTemplate代替; 相对于把一个查询操作封装成一个类而言,直接调用JdbcTemplate方法将更简单而且更容易理解。但这仅仅是一种观点而已, 如果你认为可以从直接使用RDBMS操作类中获取一些额外的好处,你不妨根据自己的需要和喜好进行不同的选择。

    15.6.1 SqlQuery

    SqlQuery类主要封装了SQL查询,本身可重用并且是线程安全的。子类必须实现newRowMapper方法,这个方法提供了一个RowMapper实例,用于在查询执行返回时创建的结果集迭代过程中每一行映射并创建一个对象。SqlQuery类一般不会直接使用;因为MappingSqlQuery子类已经提供了一个更方便从列映射到Java类的实现。其他继承SqlQuery的子类有MappingSqlQueryWithParameters和UpdatableSqlQuery。

    15.6.2 MappingSqlQuery
    MappingSqlQuery是一个可重用的查询类,它的子类必须实现mapRow(..)方法,将结果集返回的每一行转换成指定的对象类型。下面的例子展示了一个自定义的查询例子,将t_actor关系表的数据映射成Actor类。

    01 public class ActorMappingQuery extends MappingSqlQuery {
    02  
    03     public ActorMappingQuery(DataSource ds) {
    04         super(ds, "select id, first_name, last_name from t_actor where id = ?");
    05         super.declareParameter(new SqlParameter("id", Types.INTEGER));
    06         compile();
    07     }
    08  
    09     @Override
    10     protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
    11         Actor actor = new Actor();
    12         actor.setId(rs.getLong("id"));
    13         actor.setFirstName(rs.getString("first_name"));
    14         actor.setLastName(rs.getString("last_name"));
    15         return actor;
    16     }
    17  
    18 }

    这个类继承了MappingSqlQuery,并且传入Actor类型的泛型参数。这个自定义查询类的构造函数将DataSource作为唯一的传入参数。这个构造器中你调用父类的构造器,传入DataSource以及相应的SQL参数。该SQL用于创建PreparedStatement,因此它可能包含任何在执行过程中传入参数的占位符。你必须在SqlParameter中使用declareParameter方法定义每个参数。SqlParameter使用java.sql.Types定义名字和JDBC类型。在你定义了所有的参数后,你需要调用compile方法,语句被预编译后方便后续的执行。这个类在编译后是线程安全的,一旦在DAO初始化时这些实例被创建后,它们可以作为实例变量一直被重用。

    01 private ActorMappingQuery actorMappingQuery;
    02  
    03 @Autowired
    04 public void setDataSource(DataSource dataSource) {
    05     this.actorMappingQuery = new ActorMappingQuery(dataSource);
    06 }
    07  
    08 public Customer getCustomer(Long id) {
    09     return actorMappingQuery.findObject(id);
    10 }

    这个例子中的方法通过唯一的传入参数id获取customer实例。因为我们只需要返回一个对象,所以就简单的调用findObject类就可以了,这个方法只需要传入id参数。如果我们需要一次查询返回一个列表的话,就需要使用传入可变参数数组的执行方法。

    1 public List searchForActors(int age, String namePattern) {
    2     List actors = actorSearchMappingQuery.execute(age, namePattern);
    3     return actors;
    4 }

    15.6.3 SqlUpdate
    SqlUpdate封装了SQL的更新操作。和查询一样,更新对象是可以被重用的,就像所有的rdbms操作类,更新操作能够传入参数并且在SQL定义。类似于SqlQuery诸多execute(..)方法,这个类提供了一系列update(..)方法。SQLUpdate类不是抽象类,它可以被继承,比如,实现自定义的更新方法。但是你并不需要继承SqlUpdate类来达到这个目的,你可以更简单的在SQL中设置自定义参数来实现。

    01 import java.sql.Types;
    02  
    03 import javax.sql.DataSource;
    04  
    05 import org.springframework.jdbc.core.SqlParameter;
    06 import org.springframework.jdbc.object.SqlUpdate;
    07  
    08 public class UpdateCreditRating extends SqlUpdate {
    09  
    10     public UpdateCreditRating(DataSource ds) {
    11         setDataSource(ds);
    12         setSql("update customer set credit_rating = ? where id = ?");
    13         declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
    14         declareParameter(new SqlParameter("id", Types.NUMERIC));
    15         compile();
    16     }
    17  
    18     /**
    19      * @param id for the Customer to be updated
    20      * @param rating the new value for credit rating
    21      * @return number of rows updated
    22      */
    23     public int execute(int id, int rating) {
    24         return update(rating, id);
    25     }
    26 }

    15.6.4 StoredProcedure
    StoredProcedure类是所有RDBMS存储过程的抽象类。该类提供了多种execute(..)方法,其访问类型都是protected的。

    为了定义一个存储过程类,你需要使用SqlParameter或者它的一个子类。你必须像下面的代码例子那样在构造函数中指定参数名和SQL类型。SQL类型使用java.sql.Types 常量定义。

    1 new SqlParameter("in_id", Types.NUMERIC),
    2     new SqlOutParameter("out_first_name", Types.VARCHAR),

    SqlParameter的第一行定义了一个输入参数。输入参数可以同时被存储过程调用和使用SqlQuery的查询语句使用,他的子类会在下面的章节提到。

    第二行SqlOutParameter 参数定义了一个在存储过程调用中使用的输出参数。SqlInOutParameter 还有一个InOut参数,该参数提供了一个输入值,同时也有返回值。

    对应输入参数,除了名字和SQL类型,你还能指定返回区间数值类型和自定义数据库类型。对于输出参数你可以使用RowMapper来处理REF游标返回的行映射关系。另一个选择是指定SqlReturnType,能够让你定义自定义的返回值类型。

    下面的程序演示了如何调用Oracle中的sysdate()函数。为了使用存储过程函数你需要创建一个StoredProcedure的子类。在这个例子中,StoredProcedure是一个内部类,但是如果你需要重用StoredProcedure你需要定义成一个顶级类。这个例子没有输入参数,但是使用SqlOutParameter类定义了一个时间类型的输出参数。execute()方法执行了存储过程,并且从结果集Map中获取返回的时间数据。结果集Map中包含每个输出参数对应的项,在这个例子中就只有一项,使用了参数名作为key.

    01 import java.sql.Types;
    02 import java.util.Date;
    03 import java.util.HashMap;
    04 import java.util.Map;
    05  
    06 import javax.sql.DataSource;
    07  
    08 import org.springframework.beans.factory.annotation.Autowired;
    09 import org.springframework.jdbc.core.SqlOutParameter;
    10 import org.springframework.jdbc.object.StoredProcedure;
    11  
    12 public class StoredProcedureDao {
    13  
    14     private GetSysdateProcedure getSysdate;
    15  
    16     @Autowired
    17     public void init(DataSource dataSource) {
    18         this.getSysdate = new GetSysdateProcedure(dataSource);
    19     }
    20  
    21     public Date getSysdate() {
    22         return getSysdate.execute();
    23     }
    24  
    25     private class GetSysdateProcedure extends StoredProcedure {
    26  
    27         private static final String SQL = "sysdate";
    28  
    29         public GetSysdateProcedure(DataSource dataSource) {
    30             setDataSource(dataSource);
    31             setFunction(true);
    32             setSql(SQL);
    33             declareParameter(new SqlOutParameter("date", Types.DATE));
    34             compile();
    35         }
    36  
    37         public Date execute() {
    38             // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
    39             Map results = execute(new HashMap());
    40             Date sysdate = (Date) results.get("date");
    41             return sysdate;
    42         }
    43     }
    44  
    45 }

    下面是一个包含两个输出参数的存储过程例子。

    01 import oracle.jdbc.OracleTypes;
    02 import org.springframework.jdbc.core.SqlOutParameter;
    03 import org.springframework.jdbc.object.StoredProcedure;
    04  
    05 import javax.sql.DataSource;
    06 import java.util.HashMap;
    07 import java.util.Map;
    08  
    09 public class TitlesAndGenresStoredProcedure extends StoredProcedure {
    10  
    11     private static final String SPROC_NAME = "AllTitlesAndGenres";
    12  
    13     public TitlesAndGenresStoredProcedure(DataSource dataSource) {
    14         super(dataSource, SPROC_NAME);
    15         declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
    16         declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
    17         compile();
    18     }
    19  
    20     public Map execute() {
    21         // again, this sproc has no input parameters, so an empty Map is supplied
    22         return super.execute(new HashMap());
    23     }
    24 }

    值得注意的是TitlesAndGenresStoredProcedure构造函数中 declareParameter(..)的SqlOutParameter参数, 该参数使用RowMapper接口的实现。这是一种非常方便有效的重用方式。两种RowMapper实现的代码如下:

    TitleMapper类将返回结果集的每一行映射成Title类

    01 import org.springframework.jdbc.core.RowMapper;
    02  
    03 import java.sql.ResultSet;
    04 import java.sql.SQLException;
    05  
    06 import com.foo.domain.Title;
    07  
    08 public final class TitleMapper implements RowMapper {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="plain">Title mapRow(ResultSet rs, </code><code class="keyword">int</code> <code class="plain">rowNum) </code><code class="keyword">throws</code> <code class="plain"> SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">        </code><code class="plain">Title title = </code><code class="keyword">new</code> <code class="plain">Title();</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">        </code><code class="plain">title.setId(rs.getLong(</code><code class="string">"id"</code><code class="plain">));</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="spaces">        </code><code class="plain">title.setName(rs.getString(</code><code class="string">"name"</code><code class="plain">));</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="plain">title;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>16</code></td> <td class="content"><code class="plain">}</code></td> </tr> </tbody> </table> </div> </div> </div> <p>GenreMapper类将返回结果集的每一行映射成Genre类</p> <div id="highlighter_491094" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">org.springframework.jdbc.core.RowMapper;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.sql.ResultSet;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.sql.SQLException;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">com.foo.domain.Genre;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="keyword">public</code> <code class="keyword">final</code> <code class="keyword">class</code> <code class="plain">GenreMapper </code><code class="keyword">implements</code> <code class="plain">RowMapper<Genre> {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="plain">Genre mapRow(ResultSet rs, </code><code class="keyword">int</code> <code class="plain">rowNum) </code><code class="keyword">throws</code> <code class="plain"> SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="keyword">new</code> <code class="plain">Genre(rs.getString(</code><code class="string">"name"</code><code class="plain">));</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="plain">}</code></td> </tr> </tbody> </table> </div> </div> </div> <p>为了将参数传递给RDBMS中定义的一个或多个输入参数给存储过程,你可以定义一个强类型的execute(..)方法,该方法将调用基类的protected execute(Map parameters)方法。例如:</p> <div id="highlighter_267239" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">oracle.jdbc.OracleTypes;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">org.springframework.jdbc.core.SqlOutParameter;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">org.springframework.jdbc.core.SqlParameter;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">org.springframework.jdbc.object.StoredProcedure;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">javax.sql.DataSource;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.sql.Types;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.util.Date;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.util.HashMap;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="keyword">import</code> <code class="plain">java.util.Map;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="keyword">public</code> <code class="keyword">class</code> <code class="plain">TitlesAfterDateStoredProcedure </code><code class="keyword">extends</code> <code class="plain">StoredProcedure {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">private</code> <code class="keyword">static</code> <code class="keyword">final</code> <code class="plain"> String SPROC_NAME = </code><code class="string">"TitlesAfterDate"</code><code class="plain">;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>16</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">private</code> <code class="keyword">static</code> <code class="keyword">final</code> <code class="plain"> String CUTOFF_DATE_PARAM = </code><code class="string">"cutoffDate"</code><code class="plain">;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>17</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>18</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="plain">TitlesAfterDateStoredProcedure(DataSource dataSource) {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>19</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">super</code><code class="plain">(dataSource, SPROC_NAME);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>20</code></td> <td class="content"><code class="spaces">        </code><code class="plain">declareParameter(</code><code class="keyword">new</code> <code class="plain">SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>21</code></td> <td class="content"><code class="spaces">        </code><code class="plain">declareParameter(</code><code class="keyword">new</code> <code class="plain">SqlOutParameter(</code><code class="string">"titles"</code><code class="plain">, OracleTypes.CURSOR, </code><code class="keyword">new</code> <code class="plain">TitleMapper()));</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>22</code></td> <td class="content"><code class="spaces">        </code><code class="plain">compile();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>23</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>24</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>25</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="plain">Map<String, Object> execute(Date cutoffDate) {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>26</code></td> <td class="content"><code class="spaces">        </code><code class="plain">Map<String, Object> inputs = </code><code class="keyword">new</code> <code class="plain">HashMap<String, Object>();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>27</code></td> <td class="content"><code class="spaces">        </code><code class="plain">inputs.put(CUTOFF_DATE_PARAM, cutoffDate);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>28</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="keyword">super</code><code class="plain">.execute(inputs);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>29</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>30</code></td> <td class="content"><code class="plain">}</code></td> </tr> </tbody> </table> </div> </div> </div> <p><strong>15.7 参数和数据处理的常见问题</strong></p> <p>Spring JDBC框架提供了多个方法来处理常见的参数和数据问题</p> <p><strong>15.7.1 为参数设置SQL的类型信息</strong></p> <p>通常Sping通过传入的参数类型决定SQL的参数类型。可以在设定参数值的时候显式提供SQL类型。有些场景下设置NULL值是有必要的。</p> <p>你可以通过以下方式来设置SQL类型信息:</p> </li> <li>许多JdbcTemplate的更新和查询方法需要传入额外的int数组类型的参数。这个数组使用java.sql.Types类的常量值来确定相关参数的SQL类型。每个参数会有对应的类型项。</li> <li>你可以使用SqlParameterValue类来包装需要额外信息的参数值。针对每个值创建一个新的实例,并且在构造函数中传入SQL类型和参数值。你还可以传入数值类型的可选区间参数</li> <li>对于那些使用命名参数的情况,使用SqlParameterSource类型的类比如BeanPropertySqlParameterSource ,或MapSqlParameterSource。他们都具备了为命名参数注册SQL类型的功能。 <p><strong>15.7.2 处理BLOB和CLOB对象</strong></p> <p>你可以存储图片,其他类型的二进制数据,和数据库里面的大块文本。这些大的对象叫做BLOBS(全称:Binary Large OBject;用于二进制数据)和CLOBS(全称:Character Large OBject;用于字符数据)。在Spring中你可以使用JdbcTemplate直接处理这些大对象,并且也可以使用RDBMS对象提供的上层抽象类,或者使用SimpleJdbc类。所有这些方法使用LobHandler接口的实现类来处理LOB数据的管理(全称:Large Object)。LobHandler通过getLobCreator方法提供了对LobCreator 类的访问,用于创建新的LOB插入对象。</p> <p>LobCreator/LobHandler提供了LOB输入和输出的支持:</p> <div id="highlighter_176187" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain">BLOB:</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">  </code><code class="keyword">byte</code><code class="plain">[] — getBlobAsBytes和setBlobAsBytes</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">  </code><code class="plain">InputStream - getBlobAsBinaryStream和setBlobAsBinaryStream</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>5</code></td> <td class="content"><code class="plain">CLOB:</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>6</code></td> <td class="content"><code class="spaces">  </code><code class="plain">String - getClobAsString和setClobAsString</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>7</code></td> <td class="content"><code class="spaces">  </code><code class="plain">InputStream - getClobAsAsciiStream和setClobAsAsciiStream</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>8</code></td> <td class="content"><code class="spaces">  </code><code class="plain">Reader - getClobAsCharacterStream和setClobAsCharacterStream</code></td> </tr> </tbody> </table> </div> </div> </div> <p>下面的例子展示了如何创建和插入一个BLOB。后面的例子我们将举例如何从数据库中将BLOB数据读取出来</p> <p>这个例子使用了JdbcTemplate和AbstractLobCreatingPreparedStatementCallback的实现类。它主要实现了一个方法,setValues.这个方法提供了用于在你的SQL插入语句中设置LOB列的LobCreator。</p> <p>针对这个例子我们假定有一个变量lobHandler,已经设置了DefaultLobHandler的一个实例。通常你可以使用依赖注入来设置这个值。</p> <div id="highlighter_718196" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">File blobIn = </code><code class="keyword">new</code> <code class="plain">File(</code><code class="string">"spring2004.jpg"</code><code class="plain">);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">InputStream blobIs = </code><code class="keyword">new</code> <code class="plain">FileInputStream(blobIn);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">File clobIn = </code><code class="keyword">new</code> <code class="plain">File(</code><code class="string">"large.txt"</code><code class="plain">);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">InputStream clobIs = </code><code class="keyword">new</code> <code class="plain">FileInputStream(clobIn);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">InputStreamReader clobReader = </code><code class="keyword">new</code> <code class="plain">InputStreamReader(clobIs);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="plain">jdbcTemplate.execute(</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">    </code><code class="string">"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)"</code><code class="plain">,</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">new</code> <code class="plain">AbstractLobCreatingPreparedStatementCallback(lobHandler) { </code> <code class="value">1</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">protected</code> <code class="keyword">void</code> <code class="plain">setValues(PreparedStatement ps, LobCreator lobCreator) </code><code class="keyword">throws</code> <code class="plain">SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">            </code><code class="plain">ps.setLong(</code><code class="value">1</code><code class="plain">, 1L);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">            </code><code class="plain">lobCreator.setClobAsCharacterStream(ps, </code><code class="value">2</code><code class="plain">, clobReader, (</code><code class="keyword">int</code><code class="plain">)clobIn.length()); </code><code class="value">2</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">            </code><code class="plain">lobCreator.setBlobAsBinaryStream(ps, </code><code class="value">3</code><code class="plain">, blobIs, (</code><code class="keyword">int</code><code class="plain">)blobIn.length()); </code><code class="value">3</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="spaces">        </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="plain">);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>16</code></td> <td class="content"><code class="plain">blobIs.close();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>17</code></td> <td class="content"><code class="plain">clobReader.close();</code></td> </tr> </tbody> </table> </div> </div> </div> <p>1、这里例子中传入的lobHandler 使用了默认实现类DefaultLobHandler<br> 2、使用setClobAsCharacterStream传入CLOB的内容<br> 3、使用setBlobAsBinaryStream传入BLOB的内容</p> <blockquote> <p>备注:如果你调用从DefaultLobHandler.getLobCreator()返回的LobCreator的setBlobAsBinaryStream, setClobAsAsciiStream, 或者setClobAsCharacterStream方法,其中contentLength参数允许传入一个负值。如果指定的内容长度是负值,DefaultLobHandler会使用JDBC4.0不带长度参数的set-stream方法,或者直接传入驱动指定的长度;JDBC驱动对未指定长度的LOB流的支持请参见相关文档</p> </blockquote> <p>下面是从数据库读取LOB数据的例子。我们这里再次使用JdbcTempate并使用相同的DefaultLobHandler实例。</p> <div id="highlighter_264613" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain">List<Map<String, Object>> l = jdbcTemplate.query(</code><code class="string">"select id, a_clob, a_blob from lob_table"</code><code class="plain">,</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">new</code> <code class="plain">RowMapper<Map<String, Object>>() {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">public</code> <code class="plain">Map<String, Object> mapRow(ResultSet rs, </code><code class="keyword">int</code> <code class="plain">i) </code><code class="keyword">throws</code> <code class="plain"> SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="spaces">            </code><code class="plain">Map<String, Object> results = </code><code class="keyword">new</code> <code class="plain">HashMap<String, Object>();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>5</code></td> <td class="content"><code class="spaces">            </code><code class="plain">String clobText = lobHandler.getClobAsString(rs, </code><code class="string">"a_clob"</code><code class="plain">); </code><code class="value">1</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>6</code></td> <td class="content"><code class="plain">results.put(</code><code class="string">"CLOB"</code><code class="plain">, clobText); </code><code class="keyword">byte</code><code class="plain">[] blobBytes = lobHandler.getBlobAsBytes(rs, </code><code class="string">"a_blob"</code><code class="plain">); </code><code class="value">2</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>7</code></td> <td class="content"><code class="plain">results.put(</code><code class="string">"BLOB"</code><code class="plain">, blobBytes); </code><code class="keyword">return</code> <code class="plain">results; } });</code></td> </tr> </tbody> </table> </div> </div> </div> <p>1、使用getClobAsString获取CLOB的内容<br> 2、使用getBlobAsBytes获取BLOC的内容</p> <p>15.7.3 传入IN语句的列表值</p> <p>SQL标准允许基于一个带参数列表的表达式进行查询,一个典型的例子是select * from T_ACTOR where id in (1, 2, 3). 这样的可变参数列表没有被JDBC标准直接支持;你不能定义可变数量的占位符(placeholder),只能定义固定变量的占位符,或者你在动态生成SQL字符串的时候需要提前知晓所需占位符的数量。NamedParameterJdbcTemplate 和 JdbcTemplate 都使用了后面那种方式。当你传入参数时,你需要传入一个java.util.List类型,支持基本类型。而这个list将会在SQL执行时替换占位符并传入参数。</p> <blockquote> <p>备注:在传入多个值的时候需要注意。JDBC标准不保证你能在一个in表达式列表中传入超过100个值,不少数据库会超过这个值,但是一般都会有个上限。比如Oracle的上限是1000.</p> </blockquote> <p>除了值列表的元数据值,你可以创建java.util.List的对象数组。这个列表支持多个在in语句内定义的表达式例如<br> select * from T_ACTOR where (id, last_name) in ((1, ‘Johnson’), (2, ‘Harrop’\))。当然有个前提你的数据库需要支持这个语法。</p> <p><strong>15.7.4 处理存储过程调用的复杂类型</strong></p> <p>当你调用存储过程时有时需要使用数据库特定的复杂类型。为了兼容这些类型,当存储过程调用返回时Spring提供了一个SqlReturnType 来处理这些类型,SqlTypeValue用于存储过程的传入参数。</p> <p>下面是一个用户自定义类型ITEM_TYPE的Oracle STRUCT对象的返回值例子。SqlReturnType有一个方法getTypeValue必须被实现。而这个接口的实现将被用作SqlOutParameter声明的一部分。</p> <div id="highlighter_238579" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">TestItem = </code><code class="keyword">new</code> <code class="plain">TestItem(123L, </code> <code class="string">"A test item"</code><code class="plain">,</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">new</code> <code class="plain">SimpleDateFormat(</code><code class="string">"yyyy-M-d"</code><code class="plain">).parse(</code><code class="string">"2010-12-31"</code><code class="plain">));</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="plain">declareParameter(</code><code class="keyword">new</code> <code class="plain">SqlOutParameter(</code><code class="string">"item"</code><code class="plain">, OracleTypes.STRUCT, </code><code class="string">"ITEM_TYPE"</code><code class="plain">,</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">new</code> <code class="plain">SqlReturnType() {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">public</code> <code class="plain">Object getTypeValue(CallableStatement cs, </code><code class="keyword">int</code> <code class="plain">colIndx, </code><code class="keyword">int</code> <code class="plain"> sqlType, String typeName) </code><code class="keyword">throws</code> <code class="plain"> SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">            </code><code class="plain">STRUCT struct = (STRUCT) cs.getObject(colIndx);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">            </code><code class="plain">Object[] attr = struct.getAttributes();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="spaces">            </code><code class="plain">TestItem item = </code><code class="keyword">new</code> <code class="plain">TestItem();</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">            </code><code class="plain">item.setId(((Number) attr[</code><code class="value">0</code><code class="plain">]).longValue());</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">            </code><code class="plain">item.setDescription((String) attr[</code><code class="value">1</code><code class="plain">]);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">            </code><code class="plain">item.setExpirationDate((java.util.Date) attr[</code><code class="value">2</code><code class="plain">]);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="spaces">            </code><code class="keyword">return</code> <code class="plain">item;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"><code class="spaces">        </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}));</code></td> </tr> </tbody> </table> </div> </div> </div> <p>你可以使用SqlTypeValue 类往存储过程传入像TestItem那样的Java对象。你必须实现SqlTypeValue接口的createTypeValue方法。你可以使用传入的连接来创建像StructDescriptors这样的数据库指定对象。下面是相关的例子。</p> <div id="highlighter_639130" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="plain">SqlTypeValue value = </code><code class="keyword">new</code> <code class="plain">AbstractSqlTypeValue() {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">protected</code> <code class="plain">Object createTypeValue(Connection conn, </code><code class="keyword">int</code> <code class="plain">sqlType, String typeName) </code><code class="keyword">throws</code> <code class="plain">SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="spaces">        </code><code class="plain">StructDescriptor itemDescriptor = </code><code class="keyword">new</code> <code class="plain">StructDescriptor(typeName, conn);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="spaces">        </code><code class="plain">Struct item = </code><code class="keyword">new</code> <code class="plain">STRUCT(itemDescriptor, conn,</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">new</code> <code class="plain">Object[] {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="spaces">            </code><code class="plain">testItem.getId(),</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">            </code><code class="plain">testItem.getDescription(),</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">            </code><code class="keyword">new</code> <code class="plain">java.sql.Date(testItem.getExpirationDate().getTime())</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="spaces">        </code><code class="plain">});</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="plain">item;</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="plain">};</code></td> </tr> </tbody> </table> </div> </div> </div> <p>SqlTypeValue会加入到包含输入参数的Map中,用于执行存储过程调用。</p> <p>SqlTypeValue 的另外一个用法是给Oracle的存储过程传入一个数组。Oracle内部有它自己的ARRAY类,在这些例子中一定会被使用,你可以使用SqlTypeValue来创建Oracle ARRAY的实例,并且设置到Java ARRAY类的值中。</p> <div id="highlighter_871110" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="keyword">final</code> <code class="plain">Long[] ids = </code><code class="keyword">new</code> <code class="plain">Long[] {1L, 2L};</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="plain">SqlTypeValue value = </code><code class="keyword">new</code> <code class="plain">AbstractSqlTypeValue() {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">protected</code> <code class="plain">Object createTypeValue(Connection conn, </code><code class="keyword">int</code> <code class="plain">sqlType, String typeName) </code><code class="keyword">throws</code> <code class="plain">SQLException {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>5</code></td> <td class="content"><code class="spaces">        </code><code class="plain">ArrayDescriptor arrayDescriptor = </code><code class="keyword">new</code> <code class="plain">ArrayDescriptor(typeName, conn);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>6</code></td> <td class="content"><code class="spaces">        </code><code class="plain">ARRAY idArray = </code><code class="keyword">new</code> <code class="plain">ARRAY(arrayDescriptor, conn, ids);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>7</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="plain">idArray;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>8</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>9</code></td> <td class="content"><code class="plain">};</code></td> </tr> </tbody> </table> </div> </div> </div> <p><strong>15.8 内嵌数据库支持</strong><br> org.springframework.jdbc.datasource.embedded包包含对内嵌Java数据库引擎的支持。如对HSQL, H2, and Derby原生支持,你还可以使用扩展API来嵌入新的数据库内嵌类型和Datasource实现。</p> <p><strong>15.8.1 为什么使用一个内嵌数据库?</strong></p> <p>内嵌数据库因为比较轻量级所以在开发阶段比较方便有用。包括配置比较容易,启动快,方便测试,并且在开发阶段方便快速设计SQL操作</p> <p><strong>15.8.2 使用Spring配置来创建内嵌数据库</strong></p> <p>如果你想要将内嵌的数据库实例作为Bean配置到Spring的ApplicationContext中,使用spring-jdbc命名空间下的embedded-database tag</p> <div id="highlighter_227195" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain"><jdbc:embedded-database id=</code><code class="string">"dataSource"</code> <code class="plain">generate-name=</code><code class="string">"true"</code><code class="plain">></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:schema.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:test-data.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="plain"></jdbc:embedded-database></code></td> </tr> </tbody> </table> </div> </div> </div> <p>上面的配置创建了一个内嵌的HSQL数据库,并且在classpath下面配置schema.sql和test-data.sql资源。同时,作为一种最佳实践,内嵌数据库会被制定一个唯一生成的名字。内嵌数据库在Spring容器中作为javax.sql.DataSource Bean类型存在,并且能够被注入到所需的数据库访问对象中。</p> <p><strong>15.8.3 使用编程方式创建内嵌数据库</strong></p> <p>EmbeddedDatabaseBuilder提供了创建内嵌数据库的流式API。当你在独立的环境中或者是在独立的集成测试中可以使用这种方法创建一个内嵌数据库,下面是一个例子:</p> <div id="highlighter_440057" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="plain">EmbeddedDatabase db = </code><code class="keyword">new</code> <code class="plain">EmbeddedDatabaseBuilder()</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.generateUniqueName(</code><code class="keyword">true</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.setType(H2)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.setScriptEncoding(</code><code class="string">"UTF-8"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.ignoreFailedDrops(</code><code class="keyword">true</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.addScript(</code><code class="string">"schema.sql"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.addScripts(</code><code class="string">"user_data.sql"</code><code class="plain">, </code><code class="string">"country_data.sql"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">    </code><code class="plain">.build();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="comments">// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="plain">db.shutdown()</code></td> </tr> </tbody> </table> </div> </div> </div> <p>更多支持的细节请参见:EmbeddedDatabaseBuilder 的JavaDoc</p> <p>EmbeddedDatabaseBuilder 也可以使用Java Config类来创建内嵌数据库,下面是一个例子:</p> <div id="highlighter_847582" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="color1">@Configuration</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"><code class="keyword">public</code> <code class="keyword">class</code> <code class="plain">DataSourceConfig {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"><code class="spaces">    </code><code class="color1">@Bean</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="plain">DataSource dataSource() {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="spaces">        </code><code class="keyword">return</code> <code class="keyword">new</code> <code class="plain">EmbeddedDatabaseBuilder()</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.generateUniqueName(</code><code class="keyword">true</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.setType(H2)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.setScriptEncoding(</code><code class="string">"UTF-8"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.ignoreFailedDrops(</code><code class="keyword">true</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.addScript(</code><code class="string">"schema.sql"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.addScripts(</code><code class="string">"user_data.sql"</code><code class="plain">, </code><code class="string">"country_data.sql"</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="spaces">            </code><code class="plain">.build();</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="plain">}</code></td> </tr> </tbody> </table> </div> </div> </div> <p><strong>15.8.4 选择内嵌数据库的类型<br> 使用HSQL</strong><br> spring支持HSQL 1.8.0及以上版本。HSQL是缺省默认内嵌数据库类型。如果显式指定HSQL,设置embedded-database Tag的type属性值为HSQL。如果使用builderAPI.调用EmbeddedDatabaseType.HSQL的setType(EmbeddedDatabaseType)方法。</p> <p><strong>使用H2</strong><br> Spring也支持H2数据库。设置embedded-database tag的type类型值为H2来启用H2。如果你使用了builder API。调用setType(EmbeddedDatabaseType) 方法设置值为EmbeddedDatabaseType.H2。</p> <p><strong>使用Derby</strong><br> Spring也支持 Apache Derby 10.5及以上版本,设置embedded-database tag的type属性值为BERBY来开启DERBY。如果你使用builder API,调用setType(EmbeddedDatabaseType)方法设置值为EmbeddedDatabaseType.DERBY.</p> <p><strong>15.8.5 使用内嵌数据库测试数据访问层逻辑</strong></p> <p>内嵌数据库提供了数据访问层代码的轻量级测试方案,下面是使用了内嵌数据库的数据访问层集成测试模板。使用这样的模板当内嵌数据库不需要在测试类中被重用时是有用的。不过,当你希望创建可以在test集中共享的内嵌数据库。考虑使用 Spring TestContext测试框架,同时在Spring ApplicationContext中将内嵌数据库配置成一个Bean,具体参见15.8.2节, “使用Spring配置来创建内嵌数据库” 和15.8.3节, “使用编程方式创建内嵌数据库”.</p> <div id="highlighter_372244" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>01</code></td> <td class="content"><code class="keyword">public</code> <code class="keyword">class</code> <code class="plain">DataAccessIntegrationTestTemplate {</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>02</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>03</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">private</code> <code class="plain">EmbeddedDatabase db;</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>04</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>05</code></td> <td class="content"><code class="spaces">    </code><code class="color1">@Before</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>06</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="keyword">void</code> <code class="plain">setUp() {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>07</code></td> <td class="content"><code class="spaces">        </code><code class="comments">// creates an HSQL in-memory database populated from default scripts</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>08</code></td> <td class="content"><code class="spaces">        </code><code class="comments">// classpath:schema.sql and classpath:data.sql</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>09</code></td> <td class="content"><code class="spaces">        </code><code class="plain">db = </code> <code class="keyword">new</code> <code class="plain">EmbeddedDatabaseBuilder()</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>10</code></td> <td class="content"><code class="spaces">                </code><code class="plain">.generateUniqueName(</code><code class="keyword">true</code><code class="plain">)</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>11</code></td> <td class="content"><code class="spaces">                </code><code class="plain">.addDefaultScripts()</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>12</code></td> <td class="content"><code class="spaces">                </code><code class="plain">.build();</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>13</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>14</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>15</code></td> <td class="content"><code class="spaces">    </code><code class="color1">@Test</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>16</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="keyword">void</code> <code class="plain">testDataAccess() {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>17</code></td> <td class="content"><code class="spaces">        </code><code class="plain">JdbcTemplate template = </code><code class="keyword">new</code> <code class="plain">JdbcTemplate(db);</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>18</code></td> <td class="content"><code class="spaces">        </code><code class="plain">template.query( </code><code class="comments">/* ... */</code> <code class="plain">);</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>19</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>20</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>21</code></td> <td class="content"><code class="spaces">    </code><code class="color1">@After</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>22</code></td> <td class="content"><code class="spaces">    </code><code class="keyword">public</code> <code class="keyword">void</code> <code class="plain">tearDown() {</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>23</code></td> <td class="content"><code class="spaces">        </code><code class="plain">db.shutdown();</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>24</code></td> <td class="content"><code class="spaces">    </code><code class="plain">}</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>25</code></td> <td class="content"> </td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>26</code></td> <td class="content"><code class="plain">}</code></td> </tr> </tbody> </table> </div> </div> </div> <p><strong>15.8.6 生成内嵌数据库的唯一名字</strong><br> 开发团队在使用内嵌数据库时经常碰到的一个错误是:当他们的测试集想对同一个数据库创建额外的实例。这种错误在以下场景经常发生,XML配置文件或者@Configuration类用于创建内嵌数据库,并且相关的配置在同样的测试集的多个测试场景下都被用到(例如,在同一个JVM进程中)。例如,针对内嵌数据库的不同集成测试的ApplicationContext配置的区别只在当前哪个Bean定义是有效的。</p> <p>这些错误的根源是Spring的EmbeddedDatabaseFactory工厂( XML命名空间和Java Config对象的EmbeddedDatabaseBuilder内部都用到了这个)会将内嵌数据库的名字默认设置成”testdb”.针对的场景,内嵌数据库通常设置成和Bean Id相同的名字。(例如,常用像“dataSource”的名字)。结果,接下来创建内嵌数据库的尝试都没创建一个新的数据库。相反,同样的JDBC链接URL被重用。创建内嵌数据的库的尝试往往从同一个配置返回了已存在的内嵌数据库实例。</p> <p>为了解决这个问题Spring框架4.2 提供了生成内嵌数据库唯一名的支持。例如:</p> <div id="highlighter_206716" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain">EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="plain">EmbeddedDatabaseBuilder.generateUniqueName()</code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="plain"><jdbc:embedded-database generate-name=</code><code class="string">"true"</code> <code class="plain">…​ ></code></td> </tr> </tbody> </table> </div> </div> </div> <p><strong>15.8.7 内嵌数据库扩展支持</strong></p> <p>Spring JDBC 内嵌数据库支持以下两种扩展支持:</p> </li> <li>实现EmbeddedDatabaseConfigurer支持新的内嵌数据库类型。</li> <li>实现DataSourceFactory支持新的DataSource实现,例如管理内嵌数据库连接的连接池 <p>欢迎贡献内部扩展给Spring社区,相关网址见:jira.spring.io.</p> <p><strong>15.9 初始化Datasource</strong><br> org.springframework.jdbc.datasource.init用于支持初始化一个现有的DataSource。内嵌数据库提供了创建和初始化Datasource的一个选项,但是有时你需要在另外的服务器上初始化实例。</p> <p><strong>15.9.1 使用Spring XML来初始化数据库</strong></p> <p>如果你想要初始化一个数据库你可以设置DataSource Bean的引用,使用spring-jdbc命名空间下的initialize-database标记</p> <div id="highlighter_188198" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain"><jdbc:initialize-database data-source=</code><code class="string">"dataSource"</code><code class="plain">></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:com/foo/sql/db-schema.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:com/foo/sql/db-test-data.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="plain"></jdbc:initialize-database></code></td> </tr> </tbody> </table> </div> </div> </div> <p>上面的例子执行了数据库的两个脚本:第一个脚本创建了一个Schema,第二个往表里插入了一个测试数据集。脚本路径可以使用Spring中ant类型的查找资源的模式(例如classpath*:/com/foo/**/sql/*-data.sql)。如果使用了正则表达式,脚本会按照URL或者文件名的词法顺序执行。</p> <p>默认数据库初始器会无条件执行该脚本。有时你并不想要这么做,例如。你正在执行一个已存在测试数据集的数据库脚本。下面的通用方法会避免不小心删除数据,比如像上面的例子先创建表然后再插入-第一步在表已经存在时会失败掉。</p> <p>为了能够在创建和删除已有数据方面提供更多的控制,XML命名空间提供了更多的方式。第一个是将初始化设置开启还是关闭。这个可以根据当前环境情况设置(例如用系统变量或者环境属性Bean中获取一个布尔值)例如:</p> <div id="highlighter_626957" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain"><jdbc:initialize-database data-source=</code><code class="string">"dataSource"</code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="plain">enabled=</code><code class="string">"#{systemProperties.INITIALIZE_DATABASE}"</code><code class="plain">></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"..."</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="plain"></jdbc:initialize-database></code></td> </tr> </tbody> </table> </div> </div> </div> <p>第二个选项是控制当前数据的行为,这是为了提高容错性。你能够控制初始器来忽略SQL里面执行脚本的特定错误、例如:</p> <div id="highlighter_946763" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain"><jdbc:initialize-database data-source=</code><code class="string">"dataSource"</code> <code class="plain">ignore-failures=</code><code class="string">"DROPS"</code><code class="plain">></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"..."</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="plain"></jdbc:initialize-database></code></td> </tr> </tbody> </table> </div> </div> </div> <p>在这个例子中我们声明我们预期有时脚本会执行到一个空的数据库,那么脚本中的DROP语句会失败掉。这个失败的SQL DROP语句会被忽略,但是其他错误会抛出一个异常。这在你的SQL语句不支持DROP …​ IF EXISTS(或类似语法)时比较有用,但是你想要在重新创建时无条件的移除所有的测试数据。在这个案例中第一个脚本通常是一系列DROP语句的集合,然后是一系列Create语句</p> <p>ignore-failures选项可以被设置成NONE (默认值), DROPS (忽略失败的删除), or ALL (忽略所有失败).</p> <p>每个语句都应该以;隔开,或者;字符不存在于所用的脚本的话使用新的一行。你可以全局控制或者单针对一个脚本控制,例如:</p> <div id="highlighter_981278" class="syntaxhighlighter "> <div class="lines"> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>1</code></td> <td class="content"><code class="plain"><jdbc:initialize-database data-source=</code><code class="string">"dataSource"</code> <code class="plain">separator=</code><code class="string">"@@"</code><code class="plain">></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>2</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:com/foo/sql/db-schema.sql"</code> <code class="plain">separator=</code><code class="string">";"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>3</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:com/foo/sql/db-test-data-1.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt2"> <table> <tbody> <tr> <td class="number"><code>4</code></td> <td class="content"><code class="spaces">    </code><code class="plain"><jdbc:script location=</code><code class="string">"classpath:com/foo/sql/db-test-data-2.sql"</code><code class="plain">/></code></td> </tr> </tbody> </table> </div> <div class="line alt1"> <table> <tbody> <tr> <td class="number"><code>5</code></td> <td class="content"><code class="plain"></jdbc:initialize-database></code></td> </tr> </tbody> </table> </div> </div> </div> <p>在这个例子中,两个test-data脚本使用@@作为语句分隔符,并且只有db-schema.sql使用;.配置指定默认分隔符是@@,并且将db-schema脚本内容覆盖默认值。</p> <p>如果你需要从XML命名空间获取更多控制,你可以直接简单的使用DataSourceInitializer,并且在你的应用中将其定义为一个模块</p> <p><strong>初始化依赖数据库的其他模块</strong></p> <p>大多数应用只需要使用数据库初始器基本不会遇到其他问题了:这些基本在Spring上下文初始化之前不会使用到数据库。如果你的应用不属于这种情况则需要使用阅读余下的内容。</p> <p>数据库初始器依赖于Datasource实例,并且在初始化callback方法中执行脚本(类似于XML bean定义中的init-method,组件中的@Postconstruct方法,或是在实现了InitializingBean类的afterPropertiesSet()方法)。如果有其他bean也依赖了同样的数据源同时也在初始化回调模块中使用数据源,那么可能会存在问题因为数据还没有初始化。</p> <p>一个例子是在应用启动时缓存需要从数据库Load并且初始化数据。</p> <p>为了解决这个问题你有两个选择:改变你的缓存初始化策略将其移到更后面的阶段,或者确保数据库会首先初始化。</p> <p>如果你可以控制应用的初始化行为那么第一个选项明显更容易。下面是使用这个方式的一些建议,包括:</p> </li> <li>缓存做懒加载,并且只在第一次访问的时候初始化,这样会提高你应用启动时间。</li> <li>让你的缓存或者其他独立模块通过实现Lifecycle或者SmartLifecycle接口来初始化缓存。当应用上下文启动时如果autoStartup有设置,SmartLifecycle会被自动启动,而Lifecycle 可以在调用ConfigurableApplicationContext.start()时手动启动。</li> <li>使用Spring的ApplicationEvent或者其他类似的自定义监听事件来触发缓存初始化。ContextRefreshedEvent总是在Spring容器加载完毕的时候使用(在所有Bean被初始化之后)。这类事件钩子(hook)机制在很多时候非常有用(这也是SmartLifecycle的默认工作机制) <p>第二个选项也可以很容易实现。建议如下:</p> </li> <li>依赖Spring Beanfatory的默认行为,Bean会按照注册顺序被初始化。你可以通过在XML配置中设置顺序来指定你应用模块的初始化顺序,确保数据库和数据库初始化会首先被执行。</li> <li>将Datasource和业务模块隔离,通过将它们放在各自独立的ApplicationContext上下文实例来控制其启动顺序。(例如:父上下文包含DataSource,子上下文包含业务模块)。这种结构在Spring Web应用中很常用,同时也是一种通用做法 <div style="margin-top:15px; font-style:italic"> <p><strong>原创文章,转载请注明:</strong> 转载自并发编程网 – ifeve.com<strong>本文链接地址:</strong> 《Spring 5 官方文档》15.使用JDBC实现数据访问</p> </div> </li> </ul> </div> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1509717163835981824"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(spring5,spring)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1950230804957294592.htm" title="SpringMVC执行流程(原理),通俗易懂" target="_blank">SpringMVC执行流程(原理),通俗易懂</a> <span class="text-muted">国服冰</span> <a class="tag" taget="_blank" href="/search/SpringMVC/1.htm">SpringMVC</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a> <div>SpringMVC执行流程(原理),通俗易懂一、图解SpringMVC流程二、进一步理解Springmvc的执行流程1、导入依赖2、建立展示的视图3、web.xml4、spring配置文件springmvc-servlet5、Controller6、tomcat配置7、访问的url8、视图页面一、图解SpringMVC流程图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提</div> </li> <li><a href="/article/1950228031524106240.htm" title="Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程" target="_blank">Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程</a> <span class="text-muted">倾听铃的声</span> <a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a> <div>前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。@pdaiSpring进阶-SpringMVC实现原理之DispatcherServlet处理请求的</div> </li> <li><a href="/article/1950226390070652928.htm" title="Flowable 高级扩展:自定义元素与性能优化实战" target="_blank">Flowable 高级扩展:自定义元素与性能优化实战</a> <span class="text-muted">练习时长两年半的程序员小胡</span> <a class="tag" taget="_blank" href="/search/Flowable/1.htm">Flowable</a><a class="tag" taget="_blank" href="/search/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/1.htm">流程引擎实战指南</a><a class="tag" taget="_blank" href="/search/%E6%B5%81%E7%A8%8B%E5%9B%BE/1.htm">流程图</a><a class="tag" taget="_blank" href="/search/flowable/1.htm">flowable</a><a class="tag" taget="_blank" href="/search/BPMN/1.htm">BPMN</a><a class="tag" taget="_blank" href="/search/%E6%B5%81%E7%A8%8B%E5%BC%95%E6%93%8E/1.htm">流程引擎</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>在前五篇文章中,我们从基础概念、流程设计、API实战、SpringBoot集成,到外部系统协同,逐步构建了Flowable的应用体系。但企业级复杂场景中,原生功能往往难以满足定制化需求——比如需要特殊的审批规则网关、与决策引擎联动实现动态路由,或是在高并发场景下优化流程引擎性能。本文将聚焦Flowable的高级扩展能力,详解如何自定义流程元素、集成规则引擎,并掌握大型系统中的性能调优策略。一、自定</div> </li> <li><a href="/article/1950225381961297920.htm" title="SpringMVC的执行流程" target="_blank">SpringMVC的执行流程</a> <span class="text-muted"></span> <div>1、什么是MVCMVC是一种设计模式。MVC的原理图如下所示M-Model模型(完成业务逻辑:有javaBean构成,service+dao+entity)V-View视图(做界面的展示jsp,html……)C-Controller控制器(接收请求—>调用模型—>根据结果派发页面2、SpringMVC是什么SpringMVC是一个MVC的开源框架,SpringMVC=Struts2+Spring,</div> </li> <li><a href="/article/1950224639502381056.htm" title="2018-09-27 aop相关" target="_blank">2018-09-27 aop相关</a> <span class="text-muted">蒋超_58dc</span> <div>1.静态织入,需要使用aspectj专用的compilermaven工程可以采用:https://www.mojohaus.org/aspectj-maven-plugin/2.动态织入,配合spring,创建代理来执行3.</div> </li> <li><a href="/article/1950202684451647488.htm" title="[spring6: Mvc-网关]-源码解析" target="_blank">[spring6: Mvc-网关]-源码解析</a> <span class="text-muted"></span> <div>推荐阅读:[spring6:Mvc-函数式编程]-源码解析GatewayServerMvcAutoConfiguration@AutoConfiguration(after={HttpClientAutoConfiguration.class,RestTemplateAutoConfiguration.class,RestClientAutoConfiguration.class,FilterAu</div> </li> <li><a href="/article/1950190146074767360.htm" title="大数据技术笔记—spring入门" target="_blank">大数据技术笔记—spring入门</a> <span class="text-muted">卿卿老祖</span> <div>篇一spring介绍spring.io官网快速开始Aop面向切面编程,可以任何位置,并且可以细致到方法上连接框架与框架Spring就是IOCAOP思想有效的组织中间层对象一般都是切入service层spring组成前后端分离已学方式,前后台未分离:Spring的远程通信:明日更新创建第一个spring项目来源:科多大数据</div> </li> <li><a href="/article/1950179866523529216.htm" title="大学社团管理系统(11831)" target="_blank">大学社团管理系统(11831)</a> <span class="text-muted">codercode2022</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/echarts/1.htm">echarts</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/cloud/1.htm">cloud</a><a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a><a class="tag" taget="_blank" href="/search/java-rocketmq/1.htm">java-rocketmq</a> <div>有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示项目演示视频二、资料介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项目截图有需要的同学,源代码和配套文档领取,加文章最下方的名片哦!</div> </li> <li><a href="/article/1950178478011772928.htm" title="全面解析:Spring Gateway如何优雅处理微服务的路由转发?" target="_blank">全面解析:Spring Gateway如何优雅处理微服务的路由转发?</a> <span class="text-muted">万猫学社</span> <a class="tag" taget="_blank" href="/search/gateway/1.htm">gateway</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>SpringGateway简介SpringGateway,这是一个基于Spring5、SpringBoot2和ProjectReactor的API网关。它旨在为微服务架构提供一个简单、有效的统一的API路由、限流、熔断等功能。在微服务的世界里,SpringGateway就像一个交通警察,负责指挥和引导各个微服务之间的交通。相较于其他的网关技术,比如Nginx、Zuul等,SpringGateway</div> </li> <li><a href="/article/1950170156898512896.htm" title="登录功能详解" target="_blank">登录功能详解</a> <span class="text-muted">开往1982</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E7%99%BB%E5%BD%95/1.htm">登录</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a> <div>本项目由maven项目改造springboot项目导入依赖org.springframework.bootspring-boot-starter-parent2.7.6org.springframework.bootspring-boot-starter-web2.7.6org.springframework.bootspring-boot-starter-test2.7.5org.springf</div> </li> <li><a href="/article/1950163724190937088.htm" title="Spring Boot 2整合Druid的两种方式" target="_blank">Spring Boot 2整合Druid的两种方式</a> <span class="text-muted">玩代码</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/Druid/1.htm">Druid</a> <div>一、自定义整合Druid(非Starter方式)适用于需要完全手动控制配置的场景添加依赖(pom.xml)com.alibabadruid1.2.8org.springframework.bootspring-boot-starter-jdbc创建配置类@ConfigurationpublicclassDruidConfig{@Bean@ConfigurationProperties(prefix</div> </li> <li><a href="/article/1950163219788132352.htm" title="webSocket双向通信" target="_blank">webSocket双向通信</a> <span class="text-muted">@泽栖</span> <a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/1.htm">网络协议</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a> <div>webSocket基础使用webSocket小说明:作用浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。WebSocket与HTTP最大的区别HTTP通信是单向的,基于请求响应模式WebSocket支持双向通信。实现长连接适用场景:消息通信,视频弹幕,实时信息,等双向通信的使用需求使用:引入maven依赖org.springfram</div> </li> <li><a href="/article/1950154523955752960.htm" title="Docker" target="_blank">Docker</a> <span class="text-muted">℡余晖^</span> <a class="tag" taget="_blank" href="/search/%E9%BB%91%E9%A9%AC%E7%82%B9%E8%AF%84%E9%A1%B9%E7%9B%AE%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98%E5%92%8C%E7%AC%94%E8%AE%B0/1.htm">黑马点评项目相关问题和笔记</a><a class="tag" taget="_blank" href="/search/docker/1.htm">docker</a><a class="tag" taget="_blank" href="/search/eureka/1.htm">eureka</a><a class="tag" taget="_blank" href="/search/%E5%AE%B9%E5%99%A8/1.htm">容器</a> <div>在黑马点评项目中,在谈到Redisson解决redis的主从一致性问题时,弹幕提到了Docker,本文来简单了解一下Docker,我的初步理解运维是维护多个集群的稳定,那它和VM虚拟机的区别又是什么?,如果要更深入地理解与学习(运维工程师),可以到b站搜索专门的课程(SpringCloud)。一、Docker是什么?重新理解“容器化”的本质1.1Docker的定义Docker是一个开源的容器化平台</div> </li> <li><a href="/article/1950153513166237696.htm" title="记录自己第n次面试(n>3)" target="_blank">记录自己第n次面试(n>3)</a> <span class="text-muted">Warren98</span> <a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E8%81%8C%E5%9C%BA%E5%92%8C%E5%8F%91%E5%B1%95/1.htm">职场和发展</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>1.Spring Boot可执行JAR的内存分配答:“在Spring Boot可执行JAR中,JVM的内存通常分为两大块:堆(Heap)和栈(Stack)。堆内存:存放对象实例和数组,通过-Xms(初始)和-Xmx(最大)控制。比如java-Xms512m-Xmx1024m-jarapp.jar,表示启动时给512 MB堆,最大可以到1 024 MB。栈内存:每个线程有独立的栈帧,用来保存方法调用</div> </li> <li><a href="/article/1950148599039979520.htm" title="Java 代理机制详解:从静态代理到动态代理,彻底掌握代理模式的原理与实战" target="_blank">Java 代理机制详解:从静态代理到动态代理,彻底掌握代理模式的原理与实战</a> <span class="text-muted">大葱白菜</span> <a class="tag" taget="_blank" href="/search/java%E5%90%88%E9%9B%86/1.htm">java合集</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/%E4%B8%AA%E4%BA%BA%E5%BC%80%E5%8F%91/1.htm">个人开发</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F/1.htm">代理模式</a> <div>作为一名Java开发工程师,你一定在使用Spring、MyBatis、RPC框架等技术时接触过“代理”(Proxy)这个概念。无论是Spring的AOP(面向切面编程)、事务管理,还是远程调用、日志记录、权限控制等场景,代理机制都扮演着至关重要的角色。本文将带你全面掌握:什么是代理?静态代理与动态代理的区别JDK动态代理与CGLIB动态代理的实现原理代理模式的典型应用场景代理在主流框架中的使用(如</div> </li> <li><a href="/article/1950145702407499776.htm" title="构建高性能Web应用:深入Spring WebFlux" target="_blank">构建高性能Web应用:深入Spring WebFlux</a> <span class="text-muted">李多田</span> <div>本文还有配套的精品资源,点击获取简介:SpringWebFlux是Spring框架的一部分,支持反应式编程模型,适合高并发和低延迟Web应用。它提供了非阻塞I/O和事件驱动模型,优化了多核处理器资源的使用。SpringWebFlux拥有两种编程模式,核心组件包括WebHandler,RouterFunction,WebFilter,和WebSession。它与高性能服务器集成,并提供反应式HTTP</div> </li> <li><a href="/article/1950144189979226112.htm" title="Spring Security OAuth2.0在分布式系统中的安全实践" target="_blank">Spring Security OAuth2.0在分布式系统中的安全实践</a> <span class="text-muted"></span> <div>引言分布式系统架构下,安全认证与授权面临跨服务、高并发、多租户等挑战。SpringSecurity与OAuth2.0的结合为微服务安全提供了标准化解决方案。分布式系统中的安全挑战跨服务身份认证的复杂性令牌管理的可扩展性问题多租户场景下的权限隔离需求防止CSRF、XSS等常见攻击SpringSecurityOAuth2.0核心架构授权服务器设计@EnableAuthorizationServer配置</div> </li> <li><a href="/article/1950144062413664256.htm" title="Spring Native与GraalVM:无服务器架构的突破" target="_blank">Spring Native与GraalVM:无服务器架构的突破</a> <span class="text-muted">tmjpz04412</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/serverless/1.htm">serverless</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a> <div>SpringNative与GraalVM的关系SpringNative是Spring生态系统的一个模块,旨在支持将Spring应用编译为原生可执行文件。GraalVM是一个高性能运行时,提供原生镜像编译能力,允许将Java应用转换为独立的可执行文件。两者结合,显著提升了启动速度和内存效率。无服务器架构中的优势原生编译后的应用启动时间从秒级降至毫秒级,完美适配无服务器环境的冷启动需求。内存占用减少5</div> </li> <li><a href="/article/1950144063588069376.htm" title="Serverless架构下Spring Function的创新实践" target="_blank">Serverless架构下Spring Function的创新实践</a> <span class="text-muted">tmjpz04412</span> <a class="tag" taget="_blank" href="/search/serverless/1.htm">serverless</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>引言:Serverless与Spring生态的交汇背景介绍:云计算与Serverless架构的兴起Spring生态的演进与云原生适配性核心问题:传统Spring应用如何融入Serverless范式Serverless架构的核心特征与挑战事件驱动、弹性伸缩与按需计费冷启动问题与性能优化需求Spring应用在Serverless环境中的典型瓶颈(如依赖注入、上下文初始化)SpringFunction的</div> </li> <li><a href="/article/1950143934961348608.htm" title="Spring Boot与云原生:微服务架构的创新实践" target="_blank">Spring Boot与云原生:微服务架构的创新实践</a> <span class="text-muted">tmjpz04412</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/kubernetes/1.htm">kubernetes</a><a class="tag" taget="_blank" href="/search/%E4%BA%91%E5%8E%9F%E7%94%9F/1.htm">云原生</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/graphql/1.htm">graphql</a> <div>引言:Spring生态的演进与现状Spring框架的发展历程与核心设计理念当前Spring生态的核心组件(SpringBoot、SpringCloud、SpringData等)行业对Spring生态的依赖与创新需求SpringBoot的创新实践1.自动化配置与启动优化条件装配(@Conditional)的深度定制案例启动类加载机制与类路径扫描优化示例:通过自定义Starter实现快速集成第三方服务</div> </li> <li><a href="/article/1950143936026701824.htm" title="Spring AI与机器学习:智能应用开发新范式" target="_blank">Spring AI与机器学习:智能应用开发新范式</a> <span class="text-muted">tmjpz04412</span> <a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a> <div>SpringAI与机器学习的整合SpringAI是一个基于Spring生态的AI开发框架,旨在简化智能应用的开发流程。通过SpringAI,开发者可以快速集成机器学习模型,构建高效的智能应用。SpringAI支持多种机器学习库和框架,如TensorFlow、PyTorch和Scikit-learn,提供统一的API接口。SpringAI的核心优势在于其模块化设计和自动化配置。开发者无需关心复杂的依</div> </li> <li><a href="/article/1950143304347742208.htm" title="使用Java和Spring WebFlux构建响应式微服务" target="_blank">使用Java和Spring WebFlux构建响应式微服务</a> <span class="text-muted">微赚淘客系统开发者@聚娃科技</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>使用Java和SpringWebFlux构建响应式微服务大家好,我是微赚淘客系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何使用Java和SpringWebFlux构建响应式微服务。SpringWebFlux是Spring框架的一部分,专为创建响应式应用程序而设计。在这篇文章中,我们将介绍如何使用SpringWebFlux构建响应式微服务,包括基本概念、代码示例以及如何</div> </li> <li><a href="/article/1950141156172034048.htm" title="Spring AI Alibaba 快速入门指南(适合初学者)" target="_blank">Spring AI Alibaba 快速入门指南(适合初学者)</a> <span class="text-muted">会飞的架狗师</span> <a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>如果你是刚接触AI开发或Spring框架的初学者,不用担心,本指南会用简单易懂的语言带你一步步了解并使用SpringAIAlibaba。一、什么是SpringAIAlibaba(小白也能懂)简单来说,SpringAIAlibaba就是一个“工具包”,它把阿里巴巴的AI技术(比如通义千问大模型、向量数据库等)和大家常用的Spring框架“打包”到了一起。**打个比方:**就像你想做蛋糕(开发AI应用</div> </li> <li><a href="/article/1950141157006700544.htm" title="【Spring WebFlux】为什么 Spring 要拥抱响应式" target="_blank">【Spring WebFlux】为什么 Spring 要拥抱响应式</a> <span class="text-muted">会飞的架狗师</span> <a class="tag" taget="_blank" href="/search/Spring/1.htm">Spring</a><a class="tag" taget="_blank" href="/search/WebFlux/1.htm">WebFlux</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a> <div>在现代分布式系统中,响应式系统已成为应对高并发、低延迟需求的核心方案。但构建响应式系统并非易事——它需要框架级别的支持来解决异步处理、资源调度、背压控制等底层问题。作为Java生态中最具影响力的框架,Spring对响应式的支持并非偶然,而是技术演进的必然选择。本文将从响应式系统的构建挑战出发,剖析Spring拥抱响应式的底层逻辑。一、响应式系统的构建困境:现有方案的局限性响应式系统的核心诉求是在有</div> </li> <li><a href="/article/1950141029860569088.htm" title="使用Spring Boot构建响应式应用" target="_blank">使用Spring Boot构建响应式应用</a> <span class="text-muted">微赚淘客系统@聚娃科技</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>使用SpringBoot构建响应式应用大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何利用SpringBoot构建响应式应用,以适应现代应用程序对高并发和低延迟的需求。一、什么是响应式应用?响应式应用是一种通过异步编程模型来处理并发请求和数据流的应用程序设计方式。它能够更有效地利用计算资源,提供更快的响应时间和更高</div> </li> <li><a href="/article/1950140903616212992.htm" title="Java朴实无华按天计划从入门到实战(强化速战版-66天)" target="_blank">Java朴实无华按天计划从入门到实战(强化速战版-66天)</a> <span class="text-muted">岫珩</span> <a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E6%97%B6%E9%97%B4%E5%AE%89%E6%8E%92/1.htm">时间安排</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/1.htm">学习计划</a> <div>致敬读者感谢阅读笑口常开生日快乐⬛早点睡觉博主相关博主信息博客首页专栏推荐活动信息文章目录Java朴实无华按天计划从入门到实战(强化速战版-66天)1.基础(18)1.1JavaSE核心(5天)1.2数据库与SQL(5天)1.3前端基础(8天)2.进阶(17天)2.1JavaWeb核心(5天)2.2Mybatis与Spring全家桶(6天)2.3中间件入门(4天)2.4实践项目(2天)3.高阶(1</div> </li> <li><a href="/article/1950138256133779456.htm" title="响应式编程实践:Spring Boot WebFlux构建高性能非阻塞服务" target="_blank">响应式编程实践:Spring Boot WebFlux构建高性能非阻塞服务</a> <span class="text-muted">fanxbl957</span> <a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>博主介绍:Java、Python、js全栈开发“多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)感兴趣的可以先收藏起来,希望帮助更多的人响应式编程实践:SpringBootWebFlux构建高性能非阻塞服务一、引言在当今数字化时代,互</div> </li> <li><a href="/article/1950123887568154624.htm" title="SpringBoot 核心注解详解与实战应用指南" target="_blank">SpringBoot 核心注解详解与实战应用指南</a> <span class="text-muted">策划加强小乔</span> <a class="tag" taget="_blank" href="/search/SpringBoot/1.htm">SpringBoot</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>SpringBoot通过丰富的注解简化了开发流程,提升了效率。以下从核心注解、配置注解、数据注解等方面展开详细说明,并附实战代码示例。@SpringBootApplication核心注解解析@SpringBootApplication是SpringBoot项目的核心注解,组合了三个关键功能:@Configuration:标识该类为配置类@EnableAutoConfiguration:启用自动配置</div> </li> <li><a href="/article/1950110020356075520.htm" title="JAVA后端开发——用 Spring Boot 实现定时任务" target="_blank">JAVA后端开发——用 Spring Boot 实现定时任务</a> <span class="text-muted">1candobetter</span> <a class="tag" taget="_blank" href="/search/JAVA%E5%BC%80%E5%8F%91/1.htm">JAVA开发</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>在后端开发中,执行定时任务是一个极其常见的需求,无论是每日的数据报表生成、定时的缓存清理,还是自动化同步第三方数据。借助SpringBoot内置的强大功能,我们只需几个简单的注解,就能实现稳定、可靠且极易维护的定时任务。第一步:开启定时任务的总开关(@EnableScheduling)我们首先要告诉SpringBoot:“嘿,我准备在这个项目里使用定时任务功能了,请帮我把相关的组件都准备好!”这个</div> </li> <li><a href="/article/1950061236175564800.htm" title="Spring学习笔记07——SpringBoot中关于接口文档管理的注解" target="_blank">Spring学习笔记07——SpringBoot中关于接口文档管理的注解</a> <span class="text-muted">Shaoxi Zhang</span> <a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a> <div>一、Lombok注解@Data:生成所有字段的getter/setter、toString()、equals()和hashCode()。@Getter/@Setter:单独为所有字段或指定字段生成getter/setter。importlombok.Data;@DatapublicclassUser{privateLongid;privateStringname;}编译后,Lombok会为id和n</div> </li> <li><a href="/article/15.htm" title="Spring4.1新特性——Spring MVC增强" target="_blank">Spring4.1新特性——Spring MVC增强</a> <span class="text-muted">jinnianshilongnian</span> <a class="tag" taget="_blank" href="/search/spring+4.1/1.htm">spring 4.1</a> <div>目录 Spring4.1新特性——综述 Spring4.1新特性——Spring核心部分及其他 Spring4.1新特性——Spring缓存框架增强 Spring4.1新特性——异步调用和事件机制的异常处理 Spring4.1新特性——数据库集成测试脚本初始化 Spring4.1新特性——Spring MVC增强 Spring4.1新特性——页面自动化测试框架Spring MVC T</div> </li> <li><a href="/article/142.htm" title="mysql 性能查询优化" target="_blank">mysql 性能查询优化</a> <span class="text-muted">annan211</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/%E4%BC%98%E5%8C%96/1.htm">优化</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E5%BA%94%E7%94%A8%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">应用服务器</a> <div> 1 时间到底花在哪了? mysql在执行查询的时候需要执行一系列的子任务,这些子任务包含了整个查询周期最重要的阶段,这其中包含了大量为了 检索数据列到存储引擎的调用以及调用后的数据处理,包括排序、分组等。在完成这些任务的时候,查询需要在不同的地方 花费时间,包括网络、cpu计算、生成统计信息和执行计划、锁等待等。尤其是向底层存储引擎检索数据的调用操作。这些调用需要在内存操</div> </li> <li><a href="/article/269.htm" title="windows系统配置" target="_blank">windows系统配置</a> <span class="text-muted">cherishLC</span> <a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a> <div>删除Hiberfil.sys :使用命令powercfg -h off 关闭休眠功能即可: http://jingyan.baidu.com/article/f3ad7d0fc0992e09c2345b51.html 类似的还有pagefile.sys msconfig 配置启动项 shutdown 定时关机 ipconfig 查看网络配置 ipconfig /flushdns</div> </li> <li><a href="/article/396.htm" title="人体的排毒时间" target="_blank">人体的排毒时间</a> <span class="text-muted">Array_06</span> <a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C/1.htm">工作</a> <div>======================== ||  人体的排毒时间是什么时候?|| ======================== 转载于: http://zhidao.baidu.com/link?url=ibaGlicVslAQhVdWWVevU4TMjhiKaNBWCpZ1NS6igCQ78EkNJZFsEjCjl3T5EdXU9SaPg04bh8MbY1bR</div> </li> <li><a href="/article/523.htm" title="ZooKeeper" target="_blank">ZooKeeper</a> <span class="text-muted">cugfy</span> <a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a> <div>Zookeeper是一个高性能,分布式的,开源分布式应用协调服务。它提供了简单原始的功能,分布式应用可以基于它实现更高级的服务,比如同步, 配置管理,集群管理,名空间。它被设计为易于编程,使用文件系统目录树作为数据模型。服务端跑在java上,提供java和C的客户端API。 Zookeeper是Google的Chubby一个开源的实现,是高有效和可靠的协同工作系统,Zookeeper能够用来lea</div> </li> <li><a href="/article/650.htm" title="网络爬虫的乱码处理" target="_blank">网络爬虫的乱码处理</a> <span class="text-muted">随意而生</span> <a class="tag" taget="_blank" href="/search/%E7%88%AC%E8%99%AB/1.htm">爬虫</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a> <div>下边简单总结下关于网络爬虫的乱码处理。注意,这里不仅是中文乱码,还包括一些如日文、韩文 、俄文、藏文之类的乱码处理,因为他们的解决方式 是一致的,故在此统一说明。     网络爬虫,有两种选择,一是选择nutch、hetriex,二是自写爬虫,两者在处理乱码时,原理是一致的,但前者处理乱码时,要看懂源码后进行修改才可以,所以要废劲一些;而后者更自由方便,可以在编码处理</div> </li> <li><a href="/article/777.htm" title="Xcode常用快捷键" target="_blank">Xcode常用快捷键</a> <span class="text-muted">张亚雄</span> <a class="tag" taget="_blank" href="/search/xcode/1.htm">xcode</a> <div>一、总结的常用命令:     隐藏xcode command+h     退出xcode command+q     关闭窗口 command+w     关闭所有窗口 command+option+w     关闭当前</div> </li> <li><a href="/article/904.htm" title="mongoDB索引操作" target="_blank">mongoDB索引操作</a> <span class="text-muted">adminjun</span> <a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a><a class="tag" taget="_blank" href="/search/%E7%B4%A2%E5%BC%95/1.htm">索引</a> <div>一、索引基础:    MongoDB的索引几乎与传统的关系型数据库一模一样,这其中也包括一些基本的优化技巧。下面是创建索引的命令:    > db.test.ensureIndex({"username":1})    可以通过下面的名称查看索引是否已经成功建立: &nbs</div> </li> <li><a href="/article/1031.htm" title="成都软件园实习那些话" target="_blank">成都软件园实习那些话</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/%E6%88%90%E9%83%BD+%E8%BD%AF%E4%BB%B6%E5%9B%AD+%E5%AE%9E%E4%B9%A0/1.htm">成都 软件园 实习</a> <div>无聊之中,翻了一下日志,发现上一篇经历是很久以前的事了,悔过~~   断断续续离开了学校快一年了,习惯了那里一天天的幼稚、成长的环境,到这里有点与世隔绝的感觉。不过还好,那是刚到这里时的想法,现在感觉在这挺好,不管怎么样,最要感谢的还是老师能给这么好的一次催化成长的机会,在这里确实看到了好多好多能想到或想不到的东西。   都说在外面和学校相比最明显的差距就是与人相处比较困难,因为在外面每个人都</div> </li> <li><a href="/article/1158.htm" title="Linux下FTP服务器安装及配置" target="_blank">Linux下FTP服务器安装及配置</a> <span class="text-muted">ayaoxinchao</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/FTP%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">FTP服务器</a><a class="tag" taget="_blank" href="/search/vsftp/1.htm">vsftp</a> <div>检测是否安装了FTP [root@localhost ~]# rpm -q vsftpd 如果未安装:package vsftpd is not installed  安装了则显示:vsftpd-2.0.5-28.el5累死的版本信息   安装FTP 运行yum install vsftpd命令,如[root@localhost ~]# yum install vsf</div> </li> <li><a href="/article/1285.htm" title="使用mongo-java-driver获取文档id和查找文档" target="_blank">使用mongo-java-driver获取文档id和查找文档</a> <span class="text-muted">BigBird2012</span> <a class="tag" taget="_blank" href="/search/driver/1.htm">driver</a> <div>注:本文所有代码都使用的mongo-java-driver实现。   在MongoDB中,一个集合(collection)在概念上就类似我们SQL数据库中的表(Table),这个集合包含了一系列文档(document)。一个DBObject对象表示我们想添加到集合(collection)中的一个文档(document),MongoDB会自动为我们创建的每个文档添加一个id,这个id在</div> </li> <li><a href="/article/1412.htm" title="JSONObject以及json串" target="_blank">JSONObject以及json串</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/json/1.htm">json</a><a class="tag" taget="_blank" href="/search/JSONObject/1.htm">JSONObject</a> <div>一.JAR包简介     要使程序可以运行必须引入JSON-lib包,JSON-lib包同时依赖于以下的JAR包:     1.commons-lang-2.0.jar     2.commons-beanutils-1.7.0.jar     3.commons-collections-3.1.jar &n</div> </li> <li><a href="/article/1539.htm" title="[Zookeeper学习笔记之三]Zookeeper实例创建和会话建立的异步特性" target="_blank">[Zookeeper学习笔记之三]Zookeeper实例创建和会话建立的异步特性</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a> <div>为了说明问题,看个简单的代码,   import org.apache.zookeeper.*; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocal</div> </li> <li><a href="/article/1666.htm" title="【Scala十二】Scala核心六:Trait" target="_blank">【Scala十二】Scala核心六:Trait</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/scala/1.htm">scala</a> <div>Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each c</div> </li> <li><a href="/article/1793.htm" title="weblogic version 10.3破解" target="_blank">weblogic version 10.3破解</a> <span class="text-muted">ronin47</span> <a class="tag" taget="_blank" href="/search/weblogic/1.htm">weblogic</a> <div>版本:WebLogic Server 10.3 说明:%DOMAIN_HOME%:指WebLogic Server 域(Domain)目录 例如我的做测试的域的根目录 DOMAIN_HOME=D:/Weblogic/Middleware/user_projects/domains/base_domain 1.为了保证操作安全,备份%DOMAIN_HOME%/security/Defa</div> </li> <li><a href="/article/1920.htm" title="求第n个斐波那契数" target="_blank">求第n个斐波那契数</a> <span class="text-muted">BrokenDreams</span> <div>        今天看到群友发的一个问题:写一个小程序打印第n个斐波那契数。         自己试了下,搞了好久。。。基础要加强了。           &nbs</div> </li> <li><a href="/article/2047.htm" title="读《研磨设计模式》-代码笔记-访问者模式-Visitor" target="_blank">读《研磨设计模式》-代码笔记-访问者模式-Visitor</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a> <div>声明: 本文只为方便我个人查阅和理解,详细的分析以及源代码请移步 原作者的博客http://chjavach.iteye.com/ import java.util.ArrayList; import java.util.List; interface IVisitor { //第二次分派,Visitor调用Element void visitConcret</div> </li> <li><a href="/article/2174.htm" title="MatConvNet的excise 3改为网络配置文件形式" target="_blank">MatConvNet的excise 3改为网络配置文件形式</a> <span class="text-muted">cherishLC</span> <a class="tag" taget="_blank" href="/search/matlab/1.htm">matlab</a> <div>MatConvNet为vlFeat作者写的matlab下的卷积神经网络工具包,可以使用GPU。 主页: http://www.vlfeat.org/matconvnet/ 教程: http://www.robots.ox.ac.uk/~vgg/practicals/cnn/index.html 注意:需要下载新版的MatConvNet替换掉教程中工具包中的matconvnet: http</div> </li> <li><a href="/article/2301.htm" title="ZK Timeout再讨论" target="_blank">ZK Timeout再讨论</a> <span class="text-muted">chenchao051</span> <a class="tag" taget="_blank" href="/search/zookeeper/1.htm">zookeeper</a><a class="tag" taget="_blank" href="/search/timeout/1.htm">timeout</a><a class="tag" taget="_blank" href="/search/hbase/1.htm">hbase</a> <div>http://crazyjvm.iteye.com/blog/1693757 文中提到相关超时问题,但是又出现了一个问题,我把min和max都设置成了180000,但是仍然出现了以下的异常信息: Client session timed out, have not heard from server in 154339ms for sessionid 0x13a3f7732340003</div> </li> <li><a href="/article/2428.htm" title="CASE WHEN 用法介绍" target="_blank">CASE WHEN 用法介绍</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/group+by/1.htm">group by</a><a class="tag" taget="_blank" href="/search/case+when/1.htm">case when</a> <div>CASE WHEN 用法介绍 1. CASE WHEN 表达式有两种形式 --简单Case函数  CASE sex  WHEN '1' THEN '男'  WHEN '2' THEN '女'  ELSE '其他' END  --Case搜索函数  CASE WHEN sex = '1' THEN </div> </li> <li><a href="/article/2555.htm" title="PHP技巧汇总:提高PHP性能的53个技巧" target="_blank">PHP技巧汇总:提高PHP性能的53个技巧</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div>PHP技巧汇总:提高PHP性能的53个技巧  用单引号代替双引号来包含字符串,这样做会更快一些。因为PHP会在双引号包围的字符串中搜寻变量,  单引号则不会,注意:只有echo能这么做,它是一种可以把多个字符串当作参数的函数译注:  PHP手册中说echo是语言结构,不是真正的函数,故把函数加上了双引号)。  1、如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍</div> </li> <li><a href="/article/2682.htm" title="Yii框架中CGridView的使用方法以及详细示例" target="_blank">Yii框架中CGridView的使用方法以及详细示例</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/yii/1.htm">yii</a> <div>CGridView显示一个数据项的列表中的一个表。 表中的每一行代表一个数据项的数据,和一个列通常代表一个属性的物品(一些列可能对应于复杂的表达式的属性或静态文本)。  CGridView既支持排序和分页的数据项。排序和分页可以在AJAX模式或正常的页面请求。使用CGridView的一个好处是,当用户浏览器禁用JavaScript,排序和分页自动退化普通页面请求和仍然正常运行。 实例代码如下:</div> </li> <li><a href="/article/2809.htm" title="Maven项目打包成可执行Jar文件" target="_blank">Maven项目打包成可执行Jar文件</a> <span class="text-muted">dyy_gusi</span> <a class="tag" taget="_blank" href="/search/assembly/1.htm">assembly</a> <div>Maven项目打包成可执行Jar文件 在使用Maven完成项目以后,如果是需要打包成可执行的Jar文件,我们通过eclipse的导出很麻烦,还得指定入口文件的位置,还得说明依赖的jar包,既然都使用Maven了,很重要的一个目的就是让这些繁琐的操作简单。我们可以通过插件完成这项工作,使用assembly插件。具体使用方式如下: 1、在项目中加入插件的依赖: <plugin> </div> </li> <li><a href="/article/2936.htm" title="php常见错误" target="_blank">php常见错误</a> <span class="text-muted">geeksun</span> <a class="tag" taget="_blank" href="/search/PHP/1.htm">PHP</a> <div>1.  kevent() reported that connect() failed (61: Connection refused) while connecting to upstream, client: 127.0.0.1, server: localhost, request: "GET / HTTP/1.1", upstream: "fastc</div> </li> <li><a href="/article/3063.htm" title="修改linux的用户名" target="_blank">修改linux的用户名</a> <span class="text-muted">hongtoushizi</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/change+password/1.htm">change password</a> <div>Change Linux Username 更改Linux用户名,需要修改4个系统的文件: /etc/passwd /etc/shadow /etc/group /etc/gshadow 古老/传统的方法是使用vi去直接修改,但是这有安全隐患(具体可自己搜一下),所以后来改成使用这些命令去代替: vipw vipw -s vigr vigr -s   具体的操作顺</div> </li> <li><a href="/article/3190.htm" title="第五章 常用Lua开发库1-redis、mysql、http客户端" target="_blank">第五章 常用Lua开发库1-redis、mysql、http客户端</a> <span class="text-muted">jinnianshilongnian</span> <a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/lua/1.htm">lua</a> <div>对于开发来说需要有好的生态开发库来辅助我们快速开发,而Lua中也有大多数我们需要的第三方开发库如Redis、Memcached、Mysql、Http客户端、JSON、模板引擎等。 一些常见的Lua库可以在github上搜索,https://github.com/search?utf8=%E2%9C%93&q=lua+resty。   Redis客户端 lua-resty-r</div> </li> <li><a href="/article/3317.htm" title="zkClient 监控机制实现" target="_blank">zkClient 监控机制实现</a> <span class="text-muted">liyonghui160com</span> <a class="tag" taget="_blank" href="/search/zkClient+%E7%9B%91%E6%8E%A7%E6%9C%BA%E5%88%B6%E5%AE%9E%E7%8E%B0/1.htm">zkClient 监控机制实现</a> <div>         直接使用zk的api实现业务功能比较繁琐。因为要处理session loss,session expire等异常,在发生这些异常后进行重连。又因为ZK的watcher是一次性的,如果要基于wather实现发布/订阅模式,还要自己包装一下,将一次性订阅包装成持久订阅。另外如果要使用抽象级别更高的功能,比如分布式锁,leader选举</div> </li> <li><a href="/article/3444.htm" title="在Mysql 众多表中查找一个表名或者字段名的 SQL 语句" target="_blank">在Mysql 众多表中查找一个表名或者字段名的 SQL 语句</a> <span class="text-muted">pda158</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>在Mysql 众多表中查找一个表名或者字段名的 SQL 语句:   方法一:SELECT table_name, column_name from information_schema.columns WHERE column_name LIKE 'Name';   方法二:SELECT column_name from information_schema.colum</div> </li> <li><a href="/article/3571.htm" title="程序员对英语的依赖" target="_blank">程序员对英语的依赖</a> <span class="text-muted">Smile.zeng</span> <a class="tag" taget="_blank" href="/search/%E8%8B%B1%E8%AF%AD/1.htm">英语</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E7%8C%BF/1.htm">程序猿</a> <div>1、程序员最基本的技能,至少要能写得出代码,当我们还在为建立类的时候思考用什么单词发牢骚的时候,英语与别人的差距就直接表现出来咯。 2、程序员最起码能认识开发工具里的英语单词,不然怎么知道使用这些开发工具。 3、进阶一点,就是能读懂别人的代码,有利于我们学习人家的思路和技术。 4、写的程序至少能有一定的可读性,至少要人别人能懂吧... 以上一些问题,充分说明了英语对程序猿的重要性。骚年</div> </li> <li><a href="/article/3698.htm" title="Oracle学习笔记(8) 使用PLSQL编写触发器" target="_blank">Oracle学习笔记(8) 使用PLSQL编写触发器</a> <span class="text-muted">vipbooks</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B/1.htm">编程</a><a class="tag" taget="_blank" href="/search/%E6%B4%BB%E5%8A%A8/1.htm">活动</a><a class="tag" taget="_blank" href="/search/Access/1.htm">Access</a> <div>    时间过得真快啊,转眼就到了Oracle学习笔记的最后个章节了,通过前面七章的学习大家应该对Oracle编程有了一定了了解了吧,这东东如果一段时间不用很快就会忘记了,所以我会把自己学习过的东西做好详细的笔记,用到的时候可以随时查找,马上上手!希望这些笔记能对大家有些帮助!     这是第八章的学习笔记,学习完第七章的子程序和包之后</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>