Spring JDBC框架

Spring JDBC簡(jiǎn)介

先來(lái)看看一個(gè)JDBC的例子。我們可以看到為了執(zhí)行一條SQL語(yǔ)句,我們需要?jiǎng)?chuàng)建連接,創(chuàng)建語(yǔ)句對(duì)象,然后執(zhí)行SQL,然后操縱結(jié)果集獲取數(shù)據(jù)。

try(Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD)){
    List<User> users = new ArrayList<>();
    try (Statement statement = connection.createStatement()) {
        try (ResultSet rs = statement.executeQuery("SELECT *FROM user")) {
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt(1));
                user.setUsername(rs.getString(2));
                user.setPassword(rs.getString(3));
                user.setNickname(rs.getString(4));
                user.setBirthday(rs.getDate(5));
                users.add(user);
            }
        }
    }
}

其實(shí)這些步驟中有很多步驟都是固定的,Spring JDBC框架將這些操作封裝起來(lái), 我們只需要關(guān)注業(yè)務(wù)邏輯點(diǎn)即可。在Spring JDBC框架中,我們要做的事情如下:

  • 定義連接字符串參數(shù)。
  • 指定SQL語(yǔ)句。
  • 聲明參數(shù)和參數(shù)類型。
  • 每次迭代結(jié)果集的操作。

Spring會(huì)幫我們完成以下事情:

  • 打開(kāi)連接。
  • 準(zhǔn)備和執(zhí)行SQL語(yǔ)句。
  • 在需要的情況下迭代結(jié)果集。
  • 處理異常。
  • 操作事務(wù)。
  • 關(guān)閉結(jié)果集、語(yǔ)句和數(shù)據(jù)庫(kù)連接。

使用JdbcTemplate

JdbcTemplate是Jdbc框架最重要的類,提供了較為底層的Jdbc操作。其它幾個(gè)類都是在JdbcTemplate基礎(chǔ)上封裝了相關(guān)功能。

添加依賴

要在Gradle項(xiàng)目中使用Spring JDBC框架,添加如下一段。由于Spring JDBC的主要類JdbcTemlate需要一個(gè)數(shù)據(jù)源用來(lái)初始化,所以還需要一個(gè)數(shù)據(jù)源的實(shí)現(xiàn)。

compile group: 'org.springframework', name: 'spring-jdbc', version: '4.3.5.RELEASE'
compile group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.1.1'

如果要使用Spring框架的其他功能,可能還需要添加對(duì)應(yīng)的依賴。

創(chuàng)建Jdbc Template Bean

首先需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)源Bean。為了將配置分離,我們先新建一個(gè)jdbc.properties文件。

jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=12345678

然后創(chuàng)建一個(gè)Spring配置文件jdbc.xml。這里用到了<context:property-placeholder>節(jié)點(diǎn)來(lái)導(dǎo)入其它配置文件。然后用這些屬性創(chuàng)建一個(gè)數(shù)據(jù)源Bean,然后再利用數(shù)據(jù)源Bean來(lái)創(chuàng)建一個(gè)JdbcTemplate。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg ref="dataSource"/>
    </bean>
    <context:property-placeholder location="jdbc.properties"/>
</beans>

JdbcTemplate操作

注冊(cè)了JdbcTemplate之后,就可以將它注入到任何地方來(lái)使用了。首先它可以使用execute方法,執(zhí)行任何SQL語(yǔ)句。這里創(chuàng)建了一個(gè)簡(jiǎn)單的MySQL用戶表,只有主鍵和用戶名。

jdbcTemplate.execute("CREATE TABLE user(id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(255) UNIQUE)");

它還可以使用update方法執(zhí)行增加、更新和刪除操作。

jdbcTemplate.update("INSERT INTO user(name) VALUES(?)", "yitian");
jdbcTemplate.update("INSERT INTO user(name) VALUES(?)", "zhang2");
jdbcTemplate.update("UPDATE user SET name=? WHERE name=?", "zhang3", "zhang2");
jdbcTemplate.update("DELETE FROM user WHERE name=?", "zhang3");

查詢操作也很簡(jiǎn)單,使用queryForObject方法,傳入SQL字符串和結(jié)果類型即可。

int count = jdbcTemplate.queryForObject("SELECT count(*) FROM user", Integer.class);
System.out.println("記錄數(shù)目是:" + count);
String name = jdbcTemplate.queryForObject("SELECT name FROM user WHERE id=1", String.class);
System.out.println("姓名是:" + name);

如果要查詢整條記錄也可以。Spring提供了一個(gè)接口RowMapper,只需要實(shí)現(xiàn)該接口的mapRow方法,即可將結(jié)果集的一條記錄轉(zhuǎn)化為一個(gè)Java對(duì)象,該方法的第二個(gè)參數(shù)是當(dāng)前行的行數(shù)。下面是一個(gè)RowMapper實(shí)現(xiàn)。

public class UserRowMapper implements RowMapper<User> {

    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new User(rs.getInt(1), rs.getString(2));
    }
}

User實(shí)體類對(duì)應(yīng)于上面創(chuàng)建表時(shí)簡(jiǎn)單的用戶表(其他方法已省略)。

public class User {
    private int id;
    private String name;
}

實(shí)現(xiàn)了RowMapper接口之后,我們就可以查詢一條記錄并轉(zhuǎn)化為Java對(duì)象了。

User user = jdbcTemplate.queryForObject("SELECT id,name FROM user WHERE id=?", new UserRowMapper(), 1);
System.out.println(user);

查詢多條記錄也可以,這時(shí)候需要使用query方法。

List<User> users = jdbcTemplate.query("SELECT id,name FROM usr", new UserRowMapper());
System.out.println(users);

還有一個(gè)通用方法queryForList,返回一個(gè)List,每一個(gè)元素都是一個(gè)Map,在Map中存放著列名和值組成的鍵值對(duì)。

List<Map<String, Object>> results = jdbcTemplate.queryForList("SELECT id,name FROM user");
System.out.println(results);

使用NamedParameterJdbcTemplate

前面的JdbcTemplate提供了非常方便的JDBC操作封裝,但是在綁定參數(shù)的時(shí)候只能采用通配符?方式以順序方式綁定參數(shù)。如果SQL語(yǔ)句比較復(fù)雜,參數(shù)比較多,那么這種方式顯得不太方便。因此Spring提供了一個(gè)更加方便的類NamedParameterJdbcTemplate,它可以以命名方式綁定SQL語(yǔ)句參數(shù)。NamedParameterJdbcTemplate在內(nèi)部使用一個(gè)JdbcTemplate,你也可以調(diào)用getJdbcOperations方法獲取底層的JdbcTemplate對(duì)象,然后用前面的方法進(jìn)行基本操作。

創(chuàng)建NamedParameterJdbcTemplate和JdbcTemplate相同,只需要傳入一個(gè)數(shù)據(jù)源即可。

<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg ref="dataSource"/>
</bean>

NamedParameterJdbcTemplate和JdbcTemplate的大部分操作相同,這里僅介紹綁定命名參數(shù)的部分。首先,SQL語(yǔ)句必須使用:參數(shù)名稱的形式作為參數(shù)。然后,我們創(chuàng)建一個(gè)MapSqlParameterSource對(duì)象,它的內(nèi)部使用了一個(gè)Map保存的命名參數(shù)的名稱和值。然后我們使用它的addValue方法傳遞需要的命名參數(shù)的名稱和值,這個(gè)方法還可以接受第三個(gè)參數(shù)指定參數(shù)類型,這個(gè)類型以java.sql.Types的一些公共字段的形式給出。最后,將MapSqlParameterSource傳遞給相應(yīng)的方法執(zhí)行即可。

String sql = "SELECT id,name FROM user WHERE name=:name AND id<:user_id";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("name", "test");
namedParameters.addValue("user_id", 100, Types.INTEGER);
User user = namedParameterJdbcTemplate.queryForObject(sql, namedParameters, new UserRowMapper());
System.out.println(user);

如果不想創(chuàng)建MapSqlParameterSource對(duì)象,還可以直接使用一個(gè)Map傳遞命名參數(shù)的名稱和值。

Map<String, Object> map = new HashMap<>();
map.put("user_id", 100);
map.put("name", "test");
List<User> users = namedParameterJdbcTemplate.query(sql, map, new UserRowMapper());
System.out.println(users);

上面討論的MapSqlParameterSource實(shí)際上實(shí)現(xiàn)了SqlParameterSource接口,上面的幾個(gè)方法簽名也是接受SqlParameterSource接口。這個(gè)接口表示用來(lái)傳遞命名參數(shù)和值的集合。除了MapSqlParameterSource之外,還有另外一個(gè)常用的實(shí)現(xiàn),BeanPropertySqlParameterSource,這個(gè)類接受一個(gè)Java Bean對(duì)象,然后使用Bean的屬性名和值作為命名參數(shù)的名稱和值。這一點(diǎn)需要注意。

User bean = new User(100, "test");
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(bean);
users = namedParameterJdbcTemplate.query(sql, parameterSource, new UserRowMapper());
System.out.println(users);

使用SimpleJdbc類

前面所說(shuō)的JdbcTemplate封裝了一些功能,讓我們方便的使用JDBC。Spring還提供了幾個(gè)更高級(jí)、功能更具體的SimpleJdbc類。這些類會(huì)讀取JDBC的元數(shù)據(jù)Metadata,使用起來(lái)更加方便。

SimpleJdbcInsert

SimpleJdbcInsert類用來(lái)插入數(shù)據(jù)。簡(jiǎn)單的使用方法如下。SimpleJdbcInsert需要一個(gè)數(shù)據(jù)源來(lái)創(chuàng)建,withTableName方法指定要插入的表名,usingGeneratedKeyColumns指定設(shè)置了主鍵自增的列名。其他使用方法和前面所說(shuō)的類類似。executeAndReturnKey這個(gè)方法很特別,它會(huì)將數(shù)據(jù)插入數(shù)據(jù)庫(kù)并返回該條記錄對(duì)應(yīng)的自增鍵。有時(shí)候我們可能希望使用自增主鍵來(lái)插入一條數(shù)據(jù),由于主鍵是數(shù)據(jù)庫(kù)自動(dòng)生成的,我們必須再次查詢數(shù)據(jù)庫(kù)才能獲得主鍵。這種情況下使用executeAndReturnKey非常方便。注意這個(gè)方法返回的是java.lang.Number類型,可以調(diào)用其XXXvalue方法轉(zhuǎn)換成各種數(shù)值。

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource)
        .withTableName("user")
        .usingGeneratedKeyColumns("id");
User user = new User();
user.setName("test");
Map<String, Object> params = new HashMap<>();
params.put("names", user.getName());
int id = simpleJdbcInsert.executeAndReturnKey(params).intValue();
System.out.println("simpleJdbcInsert" + user);

SimpleJdbcCall

SimpleJdbcCall類用來(lái)調(diào)用存儲(chǔ)過(guò)程的。使用方法類似。這里就直接給出Spring官方文檔的示例代碼了。

MySQL存儲(chǔ)過(guò)程。

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
    DECLARE out_name VARCHAR(200);
    SELECT concat(first_name, ' ', last_name)
        INTO out_name
        FROM t_actor where id = in_id;
    RETURN out_name;
END;

SimpleJdbcCall調(diào)用存儲(chǔ)過(guò)程。

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall funcGetActorName;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
                .withFunctionName("get_actor_name");
    }

    public String getActorName(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        String name = funcGetActorName.executeFunction(String.class, in);
        return name;
    }

    // ... additional methods

}

如果要從存儲(chǔ)過(guò)程獲取記錄的話,可以這樣。以下是一個(gè)MySQL存儲(chǔ)過(guò)程。

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

相對(duì)應(yīng)的Java代碼。

public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadAllActors;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_all_actors")
                .returningResultSet("actors",
                BeanPropertyRowMapper.newInstance(Actor.class));
    }

    public List getActorsList() {
        Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
        return (List) m.get("actors");
    }

    // ... additional methods

}

將JDBC操作轉(zhuǎn)化為Java對(duì)象

org.springframework.jdbc.object包下提供了一組類,讓我們用更加面向?qū)ο蟮姆绞絹?lái)操作數(shù)據(jù)庫(kù)。我們可以將SQL查詢轉(zhuǎn)化為一組業(yè)務(wù)對(duì)象,也可以方便的進(jìn)行查詢、更新和執(zhí)行存儲(chǔ)過(guò)程的操作。

MappingSqlQuery

MappingSqlQuery是一個(gè)抽象類,繼承自SQLQuery。我們?cè)谑褂眠@個(gè)類的時(shí)候需要?jiǎng)?chuàng)建一個(gè)自定義類,繼承自MappingSqlQuery,然后在其構(gòu)造方法中初始化一個(gè)查詢字符串,并在這里設(shè)置查詢參數(shù);然后需要實(shí)現(xiàn)該類的mapRow方法,將結(jié)果集的行轉(zhuǎn)化為實(shí)體類對(duì)象。下面是一個(gè)例子。構(gòu)造方法中定義的查詢字符串會(huì)被創(chuàng)建為PreparedStatement,因此可以在查詢字符串中使用占位符?。對(duì)于每個(gè)出現(xiàn)的占位符,我們都必須調(diào)用declareParameter方法聲明參數(shù),該方法接受一個(gè)SqlParameter對(duì)象,該對(duì)象需要參數(shù)名和類型兩個(gè)參數(shù)。最后需要調(diào)用compile方法編譯和準(zhǔn)備查詢。該類是線程安全的,因此可以安全的在多個(gè)線程之間共享對(duì)象。

public class UserMappingSqlQuery extends MappingSqlQuery<User> {
    public UserMappingSqlQuery(DataSource ds) {
        super(ds, "SELECT id,name FROM user WHERE id=:id");
        super.declareParameter(new SqlParameter("id", Types.INTEGER));
        compile();
    }

    @Override
    protected User mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new User(rs.getInt(1), rs.getString(2));
    }
}

然后我們創(chuàng)建一個(gè)對(duì)象,調(diào)用findObject方法并傳入查詢參數(shù),即可獲得結(jié)果對(duì)象。

@Test
public void testMappingSqlQuery() {
    MappingSqlQuery<User> mappingSqlQuery = new UserMappingSqlQuery(dataSource);
    User user = mappingSqlQuery.findObject(1);
    logger.debug(user);
}

如果查詢要返回一組記錄并傳遞多個(gè)查詢參數(shù)。需要調(diào)用相應(yīng)的execute方法。一下是另一個(gè)MappingSqlQuery,以及其測(cè)試代碼。

public class UsersMappingSqlQuery extends MappingSqlQuery<User> {
    public UsersMappingSqlQuery(DataSource ds) {
        super(ds, "SELECT id,name FROM user WHERE id<? AND name LIKE ?");
        super.declareParameter(new SqlParameter("id", Types.INTEGER));
        super.declareParameter(new SqlParameter("name", Types.VARCHAR));
        compile();
    }

    @Override
    protected User mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new User(rs.getInt(1), rs.getString(2));
    }
}
//獲取多個(gè)對(duì)象
mappingSqlQuery = new UsersMappingSqlQuery(dataSource);
List<User> users = mappingSqlQuery.execute(100, "test");
logger.debug(users);

使用SqlUpdate

這個(gè)類的使用方法和SqlQuery類似,但是由于它是一個(gè)具體類,因此不需要定義子類即可使用。下面是它的簡(jiǎn)單使用方法。為了更新具體的數(shù)據(jù)(例如一個(gè)Java Bean對(duì)象),你也可以繼承該類,并提供自己的更新方法,就和上面一樣。

@Test
public void testSqlUpdate() {
    SqlUpdate sqlUpdate = new SqlUpdate(dataSource, "INSERT INTO user(name) VALUES(?)");
    sqlUpdate.declareParameter(new SqlParameter("name", Types.VARCHAR));
    sqlUpdate.compile();
    sqlUpdate.update("wang5");

    List<User> users = jdbcTemplate.query("SELECT id,name FROM user", new UserRowMapper());
    logger.debug(users);
}

使用StoredProcedure

StoredProcedure是關(guān)系數(shù)據(jù)庫(kù)中存儲(chǔ)過(guò)程概念的抽象類,提供了一組方便的受保護(hù)方法。因此在使用該類的時(shí)候需要我們創(chuàng)建一個(gè)子類,繼承該類。在使用這個(gè)類的時(shí)候我們需要使用setSql方法設(shè)置數(shù)據(jù)庫(kù)中存儲(chǔ)過(guò)程的名稱。在傳遞參數(shù)的時(shí)候,使用SqlParameter傳遞IN參數(shù),使用SqlOutParameter傳遞OUT參數(shù),使用SqlInOutParameter傳遞INOUT參數(shù)。

以下是Spring官方文檔的一個(gè)例子。

class GetSysdateProcedure extends StoredProcedure {

    private static final String SQL = "sysdate";

    public GetSysdateProcedure(DataSource dataSource) {
        setDataSource(dataSource);
        setFunction(true);
        setSql(SQL);
        declareParameter(new SqlOutParameter("date", Types.DATE));
        compile();
    }

    public Date execute() {
        // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
        Map<String, Object> results = execute(new HashMap<String, Object>());
        Date sysdate = (Date) results.get("date");
        return sysdate;
    }
}

其他知識(shí)

提供SQL參數(shù)信息

一般情況下Spring可以自行決定SQL參數(shù)的類型,但是有時(shí)候或者說(shuō)最好由我們提供準(zhǔn)確的SQL參數(shù)信息。

  • JdbcTemplate的很多查詢和更新方法包含一個(gè)額外的參數(shù),一個(gè)int數(shù)組,該數(shù)組應(yīng)該是java.sql.Types指定的一些常量,表明SQL參數(shù)的類型。
  • 可以使用SqlParameterValue來(lái)設(shè)置參數(shù)的值,在創(chuàng)建該對(duì)象的時(shí)候提供參數(shù)的值和類型。
  • 如果使用具有命名參數(shù)功能的類時(shí),使用SqlParameterSource類(BeanPropertySqlParameterSourceMapSqlParameterSource)來(lái)指定命名參數(shù)和其類型。

數(shù)據(jù)源

我們?cè)趯W(xué)習(xí)JDBC的時(shí)候,基本上都是從DriverManager類創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接。在實(shí)際環(huán)境中,我們應(yīng)該使用數(shù)據(jù)源(DataSource)來(lái)創(chuàng)建數(shù)據(jù)庫(kù)連接。數(shù)據(jù)源將創(chuàng)建數(shù)據(jù)庫(kù)的職責(zé)和應(yīng)用代碼分離,數(shù)據(jù)源可以交給數(shù)據(jù)庫(kù)管理員來(lái)設(shè)置,程序員只需要獲取數(shù)據(jù)源對(duì)象,然后開(kāi)發(fā)相關(guān)代碼。

在上面的例子中我們使用的是Apache的commons-dbcp2數(shù)據(jù)源,Spring自己也實(shí)現(xiàn)了幾個(gè)數(shù)據(jù)源方便我們開(kāi)發(fā)和測(cè)試。

DriverManagerDataSource是一個(gè)簡(jiǎn)單的數(shù)據(jù)源,每次請(qǐng)求都會(huì)返回一個(gè)新的數(shù)據(jù)庫(kù)連接。它使用數(shù)據(jù)庫(kù)驅(qū)動(dòng)來(lái)創(chuàng)建數(shù)據(jù)源,就像我們使用DriverManager那樣。這是一個(gè)簡(jiǎn)單的測(cè)試類,可以幫助我們?cè)诓唤柚魏蜫ava EE容器的情況下獲取數(shù)據(jù)源。但是由于使用commons-dbcp2這樣的成熟數(shù)據(jù)源也很容易,所以其實(shí)我們只需要使用commons-dbcp2即可。

SingleConnectionDataSource也是一個(gè)數(shù)據(jù)源,它包裝了一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)連接,在每次請(qǐng)求都會(huì)返回同一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象。和DriverManagerDataSource相比它更輕量,因?yàn)闆](méi)有創(chuàng)建額外數(shù)據(jù)庫(kù)連接的開(kāi)銷。

初始化數(shù)據(jù)源

在創(chuàng)建數(shù)據(jù)源的時(shí)候我們可以在Spring配置文件中設(shè)置數(shù)據(jù)源的初始化腳本。

<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
    <jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
</jdbc:initialize-database>

有時(shí)候我們希望在初始化數(shù)據(jù)的時(shí)候刪除上一次的測(cè)試數(shù)據(jù)。但是如果數(shù)據(jù)庫(kù)不支持類似DROP TABLE IF EXISTS這樣的語(yǔ)法,那么我們就必須在初始化腳本中添加一些DROP語(yǔ)句。這些刪除語(yǔ)句可能會(huì)失敗(如果沒(méi)有測(cè)試數(shù)據(jù)的情況下執(zhí)行刪除),這時(shí)候就可以忽略刪除失敗。當(dāng)初始化腳本出現(xiàn)錯(cuò)誤的時(shí)候就會(huì)拋出異常,但是如果設(shè)置了忽略刪除失敗,Spring就會(huì)直接忽略這些失敗而不拋出異常。ignore-failures屬性還可以取另外兩個(gè)值NONEALL,分別表示不忽略失敗和忽略所有失敗。

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
    <jdbc:script location="..."/>
</jdbc:initialize-database>

我們還可以設(shè)置初始化腳本的間隔符。

<jdbc:initialize-database data-source="dataSource" separator="@@">
    <jdbc:script location="classpath:com/foo/sql/db-schema.sql" separator=";"/>
    <jdbc:script location="classpath:com/foo/sql/db-test-data-1.sql"/>
    <jdbc:script location="classpath:com/foo/sql/db-test-data-2.sql"/>
</jdbc:initialize-database>

工具類

org.springframework.jdbc.datasource.DataSourceUtils,這是一個(gè)方便的工具類,包含了一組和數(shù)據(jù)源相關(guān)的工具方法。

org.springframework.jdbc.support.JdbcUtils類提供了一些方法來(lái)操作JDBC,在Spring內(nèi)部使用,也可以用于自己的JDBC操作。

還有幾個(gè)工具類主要由Spring內(nèi)部使用,這里就不列舉了。

嵌入式數(shù)據(jù)庫(kù)支持

我們?cè)陂_(kāi)發(fā)數(shù)據(jù)庫(kù)應(yīng)用的時(shí)候需要安裝某種類型的數(shù)據(jù)庫(kù),比如MySQL等等。但是這樣就需要額外的項(xiàng)目依賴。這樣一個(gè)產(chǎn)品級(jí)的數(shù)據(jù)庫(kù)軟件動(dòng)輒上G,安裝、測(cè)試都不方便。這時(shí)候我們可以使用嵌入式數(shù)據(jù)庫(kù)進(jìn)行開(kāi)發(fā)和測(cè)試。嵌入式數(shù)據(jù)庫(kù)具有占用小、啟動(dòng)快、配置簡(jiǎn)單等特點(diǎn),非常適合開(kāi)發(fā)測(cè)試。而且由于嵌入式數(shù)據(jù)庫(kù)系統(tǒng)占用低,在一些設(shè)備上還可以直接作為存儲(chǔ)數(shù)據(jù)庫(kù)使用。例如輕量級(jí)嵌入式數(shù)據(jù)庫(kù)Sqlite,就安裝在每個(gè)安卓手機(jī)中,用于存儲(chǔ)數(shù)據(jù)。

在Spring中創(chuàng)建一個(gè)嵌入式數(shù)據(jù)庫(kù),在XML中添加如下一段。這樣創(chuàng)建出來(lái)的數(shù)據(jù)庫(kù)可以直接作為javax.sql.DataSource類型的Spring Bean使用。最好設(shè)置generate-name="true"生成一個(gè)唯一名稱。默認(rèn)情況下創(chuàng)建的是HSQL嵌入式數(shù)據(jù)庫(kù)。當(dāng)然別忘了添加相應(yīng)嵌入式數(shù)據(jù)庫(kù)的依賴項(xiàng)。

<jdbc:embedded-database id="dataSource" generate-name="true">
    <jdbc:script location="classpath:schema.sql"/>
    <jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>

當(dāng)然還可以以編程方式創(chuàng)建嵌入式數(shù)據(jù)庫(kù)。下面是上面等價(jià)的編程方式。

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .generateUniqueName(true)
            .setType(H2)
            .setScriptEncoding("UTF-8")
            .ignoreFailedDrops(true)
            .addScript("schema.sql")
            .addScripts("user_data.sql", "country_data.sql")
            .build();
    }
}

除了HSQL,Spring還支持H2和Derby兩種嵌入式數(shù)據(jù)庫(kù)(值得一提的是,現(xiàn)在的JDK分發(fā)包中附帶了一個(gè)Java DB數(shù)據(jù)庫(kù),在安裝了JDK之后可以在JDK安裝目錄中看到db文件夾,這里面存放的其實(shí)就是Derby數(shù)據(jù)庫(kù))。要指定數(shù)據(jù)庫(kù)類型,在上面的XML片段中添加embedded-database屬性并設(shè)置HSQLH2Derby。如果使用編程方式,在EmbeddedDatabaseBuilder上調(diào)用setType(EmbeddedDatabaseType)方法,該方法的參數(shù)指定一個(gè)枚舉,指定這三種類型。

上面的generate-name="true"或者generateUniqueName(true)挺重要的。如果不指定這個(gè)屬性。在多次調(diào)用嵌入式數(shù)據(jù)庫(kù)之后,可能會(huì)生成多個(gè)數(shù)據(jù)庫(kù)實(shí)例。為了避免這種情況發(fā)生,需要設(shè)置這個(gè)屬性。設(shè)置之后,如果已經(jīng)存在了數(shù)據(jù)庫(kù)實(shí)例,就會(huì)使用這個(gè)已存在的實(shí)例,而不是設(shè)置新的實(shí)例。這個(gè)屬性是在Spring 4.2中增加的。使用以下幾個(gè)方法都可以設(shè)置該屬性。

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()
  • EmbeddedDatabaseBuilder.generateUniqueName()
  • <jdbc:embedded-database generate-name="true" …? >

參考資料

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#jdbc

項(xiàng)目代碼在Csdn代碼庫(kù),有興趣的同學(xué)可以看看。

最后祝大家新年快樂(lè)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,923評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,951評(píng)論 6 342
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,347評(píng)論 0 16
  • #百日生涯營(yíng)# 20170426 24/100 周三 今天是打卡第24天,開(kāi)啟第四周學(xué)習(xí): ?【每日一思】我們分享...
    AilwaDong老姐姐閱讀 178評(píng)論 0 0
  • 賽里木湖是新疆海拔最高、面積最大、風(fēng)光秀麗的高山湖泊,又是大西洋暖濕氣流最后眷顧的地方,因此有“大西洋最后一...
    瑞真reason閱讀 691評(píng)論 0 1