《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/1835479758033481728.htm" title="SpringBlade dict-biz/list 接口 SQL 注入漏洞" target="_blank">SpringBlade dict-biz/list 接口 SQL 注入漏洞</a> <span class="text-muted">文章永久免费只为良心</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>SpringBladedict-biz/list接口SQL注入漏洞POC:构造请求包查看返回包你的网址/api/blade-system/dict-biz/list?updatexml(1,concat(0x7e,md5(1),0x7e),1)=1漏洞概述在SpringBlade框架中,如果dict-biz/list接口的后台处理逻辑没有正确地对用户输入进行过滤或参数化查询(PreparedSta</div> </li> <li><a href="/article/1835458199755517952.htm" title="spring如何整合druid连接池?" target="_blank">spring如何整合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/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/junit/1.htm">junit</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/idea/1.htm">idea</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a> <div>目录spring整合druid连接池1.新建maven项目2.新建mavenModule3.导入相关依赖4.配置log4j2.xml5.配置druid.xml1)xml中如何引入properties2)下面是配置文件6.准备jdbc.propertiesJDBC配置项解释7.配置druid8.测试spring整合druid连接池1.新建maven项目打开IDE(比如IntelliJIDEA,Ecl</div> </li> <li><a href="/article/1835449364223455232.htm" title="SpringCloudAlibaba—Sentinel(限流)" target="_blank">SpringCloudAlibaba—Sentinel(限流)</a> <span class="text-muted">菜鸟爪哇</span> <div>前言:自己在学习过程的记录,借鉴别人文章,记录自己实现的步骤。借鉴文章:https://blog.csdn.net/u014494148/article/details/105484410Sentinel介绍Sentinel诞生于阿里巴巴,其主要目标是流量控制和服务熔断。Sentinel是通过限制并发线程的数量(即信号隔离)来减少不稳定资源的影响,而不是使用线程池,省去了线程切换的性能开销。当资源</div> </li> <li><a href="/article/1835448238103162880.htm" title="springboot+vue项目实战一-创建SpringBoot简单项目" target="_blank">springboot+vue项目实战一-创建SpringBoot简单项目</a> <span class="text-muted">苹果酱0567</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E6%9E%90/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><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/%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">中间件</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>这段时间抽空给女朋友搭建一个个人博客,想着记录一下建站的过程,就当做笔记吧。虽然复制zjblog只要一个小时就可以搞定一个网站,或者用cms系统,三四个小时就可以做出一个前后台都有的网站,而且想做成啥样也都行。但是就是要从新做,自己做的意义不一样,更何况,俺就是专门干这个的,嘿嘿嘿要做一个网站,而且从零开始,首先呢就是技术选型了,经过一番思量决定选择-SpringBoot做后端,前端使用Vue做一</div> </li> <li><a href="/article/1835443569968640000.htm" title="Spring MVC 全面指南:从入门到精通的详细解析" target="_blank">Spring MVC 全面指南:从入门到精通的详细解析</a> <span class="text-muted">一杯梅子酱</span> <a class="tag" taget="_blank" href="/search/%E6%8A%80%E6%9C%AF%E6%A0%88%E5%AD%A6%E4%B9%A0/1.htm">技术栈学习</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><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>引言:SpringMVC,作为Spring框架的一个重要模块,为构建Web应用提供了强大的功能和灵活性。无论是初学者还是有一定经验的开发者,掌握SpringMVC都将显著提升你的Web开发技能。本文旨在为初学者提供一个全面且易于理解的学习路径,通过详细的知识点分析和实际案例,帮助你快速上手SpringMVC,让学习过程既深刻又高效。一、SpringMVC简介1.1什么是SpringMVC?Spri</div> </li> <li><a href="/article/1835438028768768000.htm" title="Spring Boot中实现跨域请求" target="_blank">Spring Boot中实现跨域请求</a> <span class="text-muted">BABA8891</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中实现跨域请求(CORS,Cross-OriginResourceSharing)可以通过多种方式,以下是几种常见的方法:1.使用@CrossOrigin注解在SpringBoot中,你可以在控制器或者具体的请求处理方法上使用@CrossOrigin注解来允许跨域请求。在控制器上应用:importorg.springframework.web.bind.annotation.</div> </li> <li><a href="/article/1835437775344726016.htm" title="博客网站制作教程" target="_blank">博客网站制作教程</a> <span class="text-muted">2401_85194651</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a> <div>首先就是技术框架:后端:Java+SpringBoot数据库:MySQL前端:Vue.js数据库连接:JPA(JavaPersistenceAPI)1.项目结构blog-app/├──backend/│├──src/main/java/com/example/blogapp/││├──BlogApplication.java││├──config/│││└──DatabaseConfig.java</div> </li> <li><a href="/article/1835428695603507200.htm" title="RabbitMQ生产者重复机制与确认机制" target="_blank">RabbitMQ生产者重复机制与确认机制</a> <span class="text-muted">java炒饭小能手</span> <a class="tag" taget="_blank" href="/search/java-rabbitmq/1.htm">java-rabbitmq</a><a class="tag" taget="_blank" href="/search/rabbitmq/1.htm">rabbitmq</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>重复机制生产者发送消息时,出现了网络故障,导致与MQ的连接中断。为了解决这个问题,SpringAMQP提供的消息发送时的重试机制。即:当RabbitTemplate与MQ连接超时后,多次重试。需要修该发送端模块的application.yaml文件,添加下面的内容:spring:rabbitmq:connection-timeout:1s#设置MQ的连接超时时间template:retry:ena</div> </li> <li><a href="/article/1835388481887629312.htm" title="【Java】已解决:org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException" target="_blank">【Java】已解决:org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException</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%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>文章目录一、分析问题背景问题背景描述出现问题的场景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项已解决:org.springframework.jdbc.datasource.lookup.DataSourceLookupFailureException在使用Spring框架进行开发时,数据源的配置和使用是非常关键的一环。然而,有时候我们可能会遇到org.springframewo</div> </li> <li><a href="/article/1835380041006018560.htm" title="SpringBoot和SpringMVC是什么关系?SpringBoot替代SpringMVC了吗?" target="_blank">SpringBoot和SpringMVC是什么关系?SpringBoot替代SpringMVC了吗?</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/MVC/1.htm">MVC</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>SpringBoot和SpringMVC都是SpringFramework生态系统中的一部分,但它们各自扮演着不同的角色和提供不同的功能集。理解它们之间的关系,首先需要了解SpringFramework本身。SpringFrameworkSpringFramework是一个全面的、开源的应用程序开发框架,它提供了广泛的功能来支持企业应用开发的几乎所有方面。SpringFramework的核心特性之</div> </li> <li><a href="/article/1835379153730367488.htm" title="spring mvc @RequestBody String类型参数" target="_blank">spring mvc @RequestBody String类型参数</a> <span class="text-muted">zoyation</span> <a class="tag" taget="_blank" href="/search/spring-mvc/1.htm">spring-mvc</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>通过如下配置:text/html;charset=UTF-8application/json;charset=UTF-8在springmvc的Controller层使用@RequestBody接收Content-Type为application/json的数据时,默认支持Map方式和对象方式参数@RequestMapping(value="/{code}/saveUser",method=Requ</div> </li> <li><a href="/article/1835373226490949632.htm" title="Java -jar 如何在后台运行项目" target="_blank">Java -jar 如何在后台运行项目</a> <span class="text-muted">vincent_hahaha</span> <div>撸了今年阿里、头条和美团的面试,我有一个重要发现.......>>>说到运行jar包通常我们都会以下面的方式运行:java-jarspringboot-0.0.1-SNAPSHOT.jar这样运行的话会有一个问题,就是我们一关闭当前窗口就会停止运行项目,要想解决这个问题,就需要在后台运行。nohupjava-jarbabyshark-0.0.1-SNAPSHOT.jar >log.file 2>&</div> </li> <li><a href="/article/1835372847959207936.htm" title="spring security中几大组件的作用和执行顺序" target="_blank">spring security中几大组件的作用和执行顺序</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> <div>springsecurity中几大组件的作用和执行顺序在SpringSecurity中,AuthenticationProvider、GroupPermissionEvaluator、PermissionEvaluator、AbstractAuthenticationProcessingFilter、DefaultMethodSecurityExpressionHandler和ManageSecu</div> </li> <li><a href="/article/1835362136054919168.htm" title="探索Zebra4J:构建高效企业级Web应用的微服务框架" target="_blank">探索Zebra4J:构建高效企业级Web应用的微服务框架</a> <span class="text-muted">叶准鑫Natalie</span> <div>探索Zebra4J:构建高效企业级Web应用的微服务框架ZebraZebra4J/Zebra4Js基于SpringBoot的JavaWeb/Nodejs框架项目地址:https://gitcode.com/gh_mirrors/zebra/Zebra项目介绍在当今快速发展的技术环境中,构建高效、可扩展的企业级Web应用是每个开发团队的追求。Zebra4J作为一款基于SpringBoot的全新微服务</div> </li> <li><a href="/article/1835344485802930176.htm" title="基于JavaWeb开发的Java+SpringMvc+vue+element实现上海汽车博物馆平台" target="_blank">基于JavaWeb开发的Java+SpringMvc+vue+element实现上海汽车博物馆平台</a> <span class="text-muted">网顺技术团队</span> <a class="tag" taget="_blank" href="/search/%E6%88%90%E5%93%81%E7%A8%8B%E5%BA%8F%E9%A1%B9%E7%9B%AE/1.htm">成品程序项目</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/%E6%B1%BD%E8%BD%A6/1.htm">汽车</a><a class="tag" taget="_blank" href="/search/%E8%AF%BE%E7%A8%8B%E8%AE%BE%E8%AE%A1/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>基于JavaWeb开发的Java+SpringMvc+vue+element实现上海汽车博物馆平台作者主页网顺技术团队欢迎点赞收藏⭐留言文末获取源码联系方式查看下方微信号获取联系方式承接各种定制系统精彩系列推荐精彩专栏推荐订阅不然下次找不到哟Java毕设项目精品实战案例《1000套》感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人文章目录基</div> </li> <li><a href="/article/1835343473629294592.htm" title="分布式锁和spring事务管理" target="_blank">分布式锁和spring事务管理</a> <span class="text-muted">暴躁的鱼</span> <a class="tag" taget="_blank" href="/search/%E9%94%81%E5%8F%8A%E4%BA%8B%E5%8A%A1/1.htm">锁及事务</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/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> <div>最近开发一个小程序遇到一个需求需要实现分布式事务管理业务需求用户在使用小程序的过程中可以查看景点,对景点地区或者城市标记是否想去,那么需要统计一个地点被标记的人数,以及记录某个用户对某个地点是否标记为想去,用两个表存储数据,一个地点表记录改地点被标记的次数,一个用户意向表记录某个用户对某个地点是否标记为想去。由于可能有多个用户同时标记一个地点,每个用户在前端点击想去按钮之后,后台接收到请求,从数据</div> </li> <li><a href="/article/1835310380474265600.htm" title="Java面试笔记记录6" target="_blank">Java面试笔记记录6</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/%E9%9D%A2%E8%AF%95/1.htm">面试</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a> <div>1.Spring是什么?特性?有哪些模块?Spring是一个轻量级、非入侵式的控制反转Ioc和面向切面AOP的框架。特性:1.Ioc和DISpring的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring工厂用于生成Bean,并且管理Bean的生命周期,实现高内聚低耦合的设计理念。2.AOP编程Spring提供面向切面编程,可以方便实现对程序进行权限拦截、运行监控等切面功能。3</div> </li> <li><a href="/article/1835292113768640512.htm" title="Sentinel" target="_blank">Sentinel</a> <span class="text-muted">眼泪落在琴弦</span> <a class="tag" taget="_blank" href="/search/springcloud/1.htm">springcloud</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>Sentinel(服务熔断降级限流)1.引入spring-cloud-starter-alibaba-sentinel2.下载sentinel服务器3.配置application地址信息4.在控制台调整参数【默认所以流控设置保存在内存中,重启失效】5.想实时监控需每个微服务导入actuator,并配置application暴露所有端口6.自定义sentinel流控返回数据7.配置sentinel类</div> </li> <li><a href="/article/1835278504103604224.htm" title="Spring @Async 深度解读:默认线程池执行器的配置与优化" target="_blank">Spring @Async 深度解读:默认线程池执行器的配置与优化</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/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>在Spring中,@Async注解用于异步执行方法。默认情况下,@Async注解的任务是由一个线程池执行的。然而,这个默认的线程池是如何初始化的呢?本文将深入探讨这一过程,帮助你理解Spring异步任务背后的线程池执行器的初始化原理。1.@Async的基本使用首先,让我们快速回顾一下@Async的基本用法。@Async通常用于标注在需要异步执行的方法上,比如:@Servicepublicclass</div> </li> <li><a href="/article/1835277244763828224.htm" title="Sentinel实时监控不展示问题" target="_blank">Sentinel实时监控不展示问题</a> <span class="text-muted">朱杰jjj</span> <a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a><a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a> <div>问题官方插件Endpoint支持,可以实时统计出SpringBoot的健康状况和请求的调用信息在使用Endpoint特性之前需要在Maven中添加spring-boot-starter-actuator依赖,并在配置中允许Endpoints的访问。SpringBoot1.x中添加配置management.security.enabled=false。暴露的endpoint路径为/sentinelS</div> </li> <li><a href="/article/1835273340084908032.htm" title="36. MyBatis如何支持多数据库操作?如何配置不同的数据源?" target="_blank">36. MyBatis如何支持多数据库操作?如何配置不同的数据源?</a> <span class="text-muted">这孩子叫逆</span> <a class="tag" taget="_blank" href="/search/Mybatis%E7%AC%94%E8%AE%B0/1.htm">Mybatis笔记</a><a class="tag" taget="_blank" href="/search/mybatis/1.htm">mybatis</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a> <div>在许多企业级应用中,可能需要访问多个数据库。MyBatis可以通过配置多个数据源和动态切换数据源来支持多数据库操作。下面介绍如何在MyBatis中配置和使用多个数据源。1.多数据源的基本配置1.1配置多个数据源要支持多个数据源,首先需要在Spring或SpringBoot中配置不同的数据源。假设我们要连接两个数据库db1和db2,可以通过以下步骤进行配置。SpringBoot示例:applicat</div> </li> <li><a href="/article/1835262244313722880.htm" title="SpringBoot整合ES搜索引擎 实现网站热搜词及热度计算" target="_blank">SpringBoot整合ES搜索引擎 实现网站热搜词及热度计算</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/Elasticsearch/1.htm">Elasticsearch</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/elasticsearch/1.htm">elasticsearch</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/%E7%83%AD%E6%90%9C%E8%AF%8D/1.htm">热搜词</a><a class="tag" taget="_blank" href="/search/%E7%83%AD%E5%BA%A6%E8%AE%A1%E7%AE%97/1.htm">热度计算</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>博主简介:历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计,Springboot和微服务,熟悉Linux,ESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于</div> </li> <li><a href="/article/1835244090057388032.htm" title="Spring Security静态资源过滤(11)" target="_blank">Spring Security静态资源过滤(11)</a> <span class="text-muted">小黑屋说YYDS</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>在一个实际项目中,并非所有的请求都需要经过SpringSecurity过滤器,有一些特殊的请求,例如静态资源等,一般来说并不需要经过SpringSecurity过滤器链,用户如果访问这些静态资源,直接返回对应的资源即可。回顾关于WebSecurity的讲解,提到它里边维护了一个ignoredRequests变量,该变量,记录的就是所有需要被忽略的请求,这些被忽略的请求将不再经过SpringSecu</div> </li> <li><a href="/article/1835243963653648384.htm" title="Spring Security定义多个过滤器链(10)" target="_blank">Spring Security定义多个过滤器链(10)</a> <span class="text-muted">小黑屋说YYDS</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>在SpringSecurity中可以同时存在多个过滤器链,一个WebSecurityConfigurerAdapter的实例就可以配置一条过滤器链。我们来看如下一个案例:@ConfigurationpublicclassSecurityConfig{@BeanUserDetailsServiceus(){InMemoryUserDetailsManagerusers=newInMemoryUser</div> </li> <li><a href="/article/1835233629056364544.htm" title="java 技术 架构 相关文档" target="_blank">java 技术 架构 相关文档</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/%E6%9E%B6%E6%9E%84/1.htm">架构</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中,有许多不同的技术和架构,这里我将列举一些常见的Java技术和架构,并提供一些相关的文档资源。SpringFrameworkSpring是一个开源的Java/JavaEE全功能框架,以Apache许可证形式发布,提供了一种实现企业级应用的方法。官方文档:SpringFrameworkSpringBootSpringBoot是Spring的一个子项目,旨在简化创建生产级的Spring应用</div> </li> <li><a href="/article/1835229467124002816.htm" title="SpringSecurity初学总结" target="_blank">SpringSecurity初学总结</a> <span class="text-muted">weixin_66442229</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>springSecurity安全框架基于Java的安全框架主要有:SpringSecurity和Shiro介绍基础概念安全框架是对用户访问权限的控制,保证应用的安全性。其主要的工作是用户认证和用户授权|鉴权主要应用于Spring的企业应用系统,提供声明式的安全访问控制解决方案。它提供了一组可以在Spring应用上下文中配置的Bean能很好的结合Spring的DI依赖注入和AOP面向切面编程功能应用</div> </li> <li><a href="/article/1835225687028494336.htm" title="java获取applicationcontext,SpringBoot获取ApplicationContext的3种方式" target="_blank">java获取applicationcontext,SpringBoot获取ApplicationContext的3种方式</a> <span class="text-muted">花儿街参考</span> <div>ApplicationContext是什么?简单来说就是Spring中的容器,可以用来获取容器中的各种bean组件,注册监听事件,加载资源文件等功能。ApplicationContext获取的几种方式1直接使用Autowired注入@ComponentpublicclassBook1{@AutowiredprivateApplicationContextapplicationContext;pub</div> </li> <li><a href="/article/1835215731222999040.htm" title="SpringBoot 设置传入参数非必要" target="_blank">SpringBoot 设置传入参数非必要</a> <span class="text-muted">loveLifeLoveCoding</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/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>查看RequestParam源码packageorg.springframework.web.bind.annotation;importjava.lang.annotation.Documented;importjava.lang.annotation.ElementType;importjava.lang.annotation.Retention;importjava.lang.annotat</div> </li> <li><a href="/article/1835201490998882304.htm" title="SpringBoot 获取 ApplicationContext" target="_blank">SpringBoot 获取 ApplicationContext</a> <span class="text-muted">loveLifeLoveCoding</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/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>1.概念ApplicationContext是什么?简单来说就是Spring中的容器,可以用来获取容器中的各种bean组件,注册监听事件,加载资源文件等功能2.获取ApplicationContext的方式2.1.创建工具类通过此工具类,可以方便的获取bean组件,获取配置信息等importorg.springframework.beans.BeansException;importorg.spr</div> </li> <li><a href="/article/1835195308322156544.htm" title="java打印标签(机型TOSHIBA条码打印机B-EX4T)" target="_blank">java打印标签(机型TOSHIBA条码打印机B-EX4T)</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/java/1.htm">java</a> <div>java打印标签实现方式标签效果代码获取更多相关资料实现rfid写入功能实现方式打印机设置网络ipjava获取socket连接,调用TPCL指令标签效果代码packageorg.jeecg.modules.invinfo.util;importorg.jeecg.modules.invinfo.vo.BatKcInventoryDetailVo;importorg.springframework.</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>