您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何使用JDBC核心類控制進(jìn)行JDBC處理”,在日常操作中,相信很多人在如何使用JDBC核心類控制進(jìn)行JDBC處理問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何使用JDBC核心類控制進(jìn)行JDBC處理”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!
下表概述的操作序列可能最好地顯示了Spring框架JDBC
抽象提供的值。該表顯示了Spring負(fù)責(zé)哪些操作以及哪些操作是你需要做的。
Action | Spring | You |
---|---|---|
定義連接參數(shù) | X | |
打開連接 | X | |
指定SQL語(yǔ)句。 | X | |
聲明參數(shù)并提供參數(shù)值 | X | |
準(zhǔn)備并運(yùn)行該語(yǔ)句。 | X | |
設(shè)置循環(huán)以遍歷結(jié)果(如果有)。 | X | |
進(jìn)行每次迭代的工作。 | X | |
處理任何異常。 | X | |
處理異常。 | X | |
關(guān)閉連接,語(yǔ)句和結(jié)果集。 | X |
Spring框架負(fù)責(zé)處理使JDBC
成為如此乏味的API的所有底層細(xì)節(jié)。
你可以選擇幾種方法來構(gòu)成JDBC
數(shù)據(jù)庫(kù)訪問的基礎(chǔ)。除了JdbcTemplate
的三種形式之外,新的SimpleJdbcInsert
和SimpleJdbcCall
方法還優(yōu)化了數(shù)據(jù)庫(kù)元數(shù)據(jù),并且RDBMS
Object
樣式采用了一種類似于JDO Query
設(shè)計(jì)的面向?qū)ο蟮姆椒?。一旦開始使用這些方法之一,你仍然可以混合搭配以包含來自其他方法的功能。所有方法都需要兼容JDBC 2.0
的驅(qū)動(dòng)程序,而某些高級(jí)功能則需要JDBC 3.0
驅(qū)動(dòng)程序。
JdbcTemplate
是經(jīng)典且最受歡迎的Spring JDBC
方法。這種最低級(jí)別的方法和所有其他方法都在幕后使用JdbcTemplate
。
NamedParameterJdbcTemplate
包裝了一個(gè)JdbcTemplate
來提供命名參數(shù),而不是傳統(tǒng)的JDBC
?
占位符。當(dāng)你有多個(gè)SQL語(yǔ)句參數(shù)時(shí),此方法可提供更好的文檔編制和易用性。
SimpleJdbcInsert
和SimpleJdbcCall
優(yōu)化數(shù)據(jù)庫(kù)元數(shù)據(jù)以限制必要的配置量。這種方法簡(jiǎn)化了編碼,因此你僅需要提供表或過程(存儲(chǔ)過程)的名稱,并提供與列名稱匹配的參數(shù)映射。僅當(dāng)數(shù)據(jù)庫(kù)提供足夠的元數(shù)據(jù)時(shí),此方法才有效。如果數(shù)據(jù)庫(kù)不提供此元數(shù)據(jù),則必須提供參數(shù)的顯式配置。
RDBMS
對(duì)象(包括MappingSqlQuery
、SqlUpdate
和StoredProcedure
)要求你在數(shù)據(jù)訪問層初始化期間創(chuàng)建可重用且線程安全的對(duì)象。此方法以JDO
查詢?yōu)槟P停渲卸x查詢字符串、聲明參數(shù)并編譯查詢。完成后,可以使用各種參數(shù)值多次調(diào)用execute(...)
、update(...)
和findObject(...)
方法。
Spring框架的JDBC
抽象框架由四個(gè)不同的包組成:
core
: org.springframework.jdbc.core
包含JdbcTemplate
類及其各種回調(diào)接口,以及各種相關(guān)類。名為org.springframework.jdbc.core.simple
的子包包含SimpleJdbcInsert
和SimpleJdbcCall
類。另一個(gè)名為org.springframework.jdbc.core.namedparam
的子包包含NamedParameterJdbcTemplate
類和相關(guān)的支持類。請(qǐng)參閱使用JDBC核心類控制基本JDBC處理和錯(cuò)誤處理、JDBC批處理操作和使用SimpleJdbc類簡(jiǎn)化JDBC操作。
datasource
: org.springframework.jdbc.datasource
包含一個(gè)實(shí)用程序類,用于輕松訪問DataSource
和各種簡(jiǎn)單DataSource
實(shí)現(xiàn),可用于在Java EE容器之外測(cè)試和運(yùn)行未修改的JDBC
代碼。名為org.springfamework.jdbc.datasource.embedded
的子包提供了對(duì)使用Java數(shù)據(jù)庫(kù)引擎(例如HSQL
、H2
和Derby
)創(chuàng)建嵌入式數(shù)據(jù)庫(kù)的支持。請(qǐng)參閱控制數(shù)據(jù)庫(kù)連接和嵌入式數(shù)據(jù)庫(kù)支持。
object
: org.springframework.jdbc.object
包含一些類,這些類將RDBMS
查詢、更新和存儲(chǔ)過程表示為線程安全的可重用對(duì)象。請(qǐng)參閱將JDBC操作建模為Java對(duì)象。盡管查詢返回的對(duì)象自然會(huì)與數(shù)據(jù)庫(kù)斷開連接,但是JDO
對(duì)此方法進(jìn)行了建模。較高級(jí)別的JDBC
抽象依賴于org.springframework.jdbc.core
包中的較低級(jí)別的抽象。
support
: org.springframework.jdbc.support
包提供了SQLException
轉(zhuǎn)換功能和一些實(shí)用程序類。JDBC
處理期間引發(fā)的異常將轉(zhuǎn)換為org.springframework.dao
包中定義的異常。這意味著使用Spring JDBC
抽象層的代碼不需要實(shí)現(xiàn)JDBC
或RDBMS
特定的錯(cuò)誤處理。所有轉(zhuǎn)換的異常均為uncheck
異常,這使你可以選擇捕獲可從中恢復(fù)的異常,同時(shí)將其他異常傳遞到調(diào)用方。請(qǐng)參見使用SQLExceptionTranslator。
本節(jié)介紹如何使用JDBC
核心類控制JDBC
處理,包括錯(cuò)誤處理。它包括以下主題:
使用JdbcTemplate
使用NamedParameterJdbcTemplate
使用SQLExceptionTranslator
執(zhí)行語(yǔ)句
執(zhí)行查詢
更新數(shù)據(jù)庫(kù)
獲取自動(dòng)生成主鍵
JdbcTemplate
JdbcTemplate
是JDBC
的core
包中的核心類。它處理資源的創(chuàng)建和釋放,這有助于你避免常見的錯(cuò)誤,例如忘記關(guān)閉連接。它執(zhí)行核心JDBC
工作流程的基本任務(wù)(例如,語(yǔ)句創(chuàng)建和執(zhí)行),而使應(yīng)用程序代碼提供SQL并提取結(jié)果。JdbcTemplate
類:
運(yùn)行SQL查詢
更新語(yǔ)句和存儲(chǔ)過程調(diào)用
對(duì)ResultSet
實(shí)例執(zhí)行迭代并提取返回的參數(shù)值。
捕獲JDBC
異常并將其轉(zhuǎn)換為org.springframework.dao
包中定義的通用、信息量更大的異常層次結(jié)構(gòu)。 (請(qǐng)參見一致的異常層次結(jié)構(gòu))
當(dāng)將JdbcTemplate
用于代碼時(shí),只需實(shí)現(xiàn)回調(diào)接口,即可為它們提供明確定義的約定。給定JdbcTemplate
類提供的Connection
、PreparedStatementCreator
回調(diào)接口將創(chuàng)建一條準(zhǔn)備好的語(yǔ)句,提供SQL和任何必要的參數(shù)。對(duì)于CallableStatementCreator
接口(創(chuàng)建可調(diào)用語(yǔ)句)也是如此。 RowCallbackHandler
接口從ResultSet
的每一行提取值。
你可以通過直接實(shí)例化DataSource
引用在DAO實(shí)現(xiàn)中使用JdbcTemplate
,也可以在Spring IoC容器中對(duì)其進(jìn)行配置,并將其作為Bean引用提供給DAO。
應(yīng)該始終在Spring IoC容器中將
DataSource
配置為Bean。在第一種情況下,bean被直接提供給服務(wù)。在第二種情況下,將其提供給準(zhǔn)備好的模板。
此類發(fā)出的所有SQL都在DEBUG
級(jí)別下記錄,該類別對(duì)應(yīng)于模板實(shí)例的全限定類名稱(通常為JdbcTemplate
,但如果使用JdbcTemplate
類的自定義子類,則可能有所不同)。
以下各節(jié)提供了JdbcTemplate
用法的一些示例。這些示例不是JdbcTemplate
暴露的所有功能的詳盡列表。請(qǐng)參考附帶的javadoc。
查詢(SELECT)
以下查詢獲取關(guān)聯(lián)中的行數(shù):
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
以下查詢使用綁定變量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
以下查詢查找字符串:
String lastName = this.jdbcTemplate.queryForObject( "select last_name from t_actor where id = ?", String.class, 1212L);
以下查詢查找并填充單個(gè)領(lǐng)域?qū)ο螅?/p>
Actor actor = jdbcTemplate.queryForObject( "select first_name, last_name from t_actor where id = ?", (resultSet, rowNum) -> { Actor newActor = new Actor(); newActor.setFirstName(resultSet.getString("first_name")); newActor.setLastName(resultSet.getString("last_name")); return newActor; }, 1212L);
以下查詢查找并填充領(lǐng)域?qū)ο罅斜恚?/p>
List<Actor> actors = this.jdbcTemplate.query( "select first_name, last_name from t_actor", (resultSet, rowNum) -> { Actor actor = new Actor(); actor.setFirstName(resultSet.getString("first_name")); actor.setLastName(resultSet.getString("last_name")); return actor; });
如果最后兩個(gè)代碼段確實(shí)存在于同一應(yīng)用程序中,則刪除兩個(gè)RowMapper
lambda表達(dá)式中存在的重復(fù)項(xiàng)并將它們提取到單個(gè)字段中,然后可以根據(jù)需要由DAO方法引用,這是有意義的。
例如,使用前面編寫的代碼段,如下所示:
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> { Actor actor = new Actor(); actor.setFirstName(resultSet.getString("first_name")); actor.setLastName(resultSet.getString("last_name")); return actor; }; public List<Actor> findAllActors() { return this.jdbcTemplate.query( "select first_name, last_name from t_actor", actorRowMapper); }
使用JdbcTemplate更新(INSERT,UPDATE和DELETE)
您可以使用update(..)方法執(zhí)行插入,更新和刪除操作。參數(shù)值通常作為變量參數(shù)提供,或者作為對(duì)象數(shù)組提供。
下面的示例插入一個(gè)新數(shù)據(jù):
this.jdbcTemplate.update( "insert into t_actor (first_name, last_name) values (?, ?)", "Leonor", "Watling");
以下示例更新現(xiàn)有數(shù)據(jù):
this.jdbcTemplate.update( "update t_actor set last_name = ? where id = ?", "Banjo", 5276L);
下面的示例刪除一條數(shù)據(jù):
this.jdbcTemplate.update( "delete from t_actor where id = ?", Long.valueOf(actorId));
參考代碼:
org.liyong.dataaccess.starter.JdbcTemplateTransactionManagerIocContainer
其他JdbcTemplate操作
你可以使用execute(..)方法來運(yùn)行任意SQL。因此,該方法經(jīng)常用于DDL語(yǔ)句。它被接受回調(diào)接口、綁定變量數(shù)組等的變量重載。下面的示例創(chuàng)建了一個(gè)表:
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
下面的示例調(diào)用一個(gè)存儲(chǔ)過程:
this.jdbcTemplate.update( "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", Long.valueOf(unionId));
稍后將介紹更復(fù)雜的存儲(chǔ)過程支持。
JdbcTemplate最佳做法
一旦配置,JdbcTemplate
類的實(shí)例是線程安全的。它很重要,因?yàn)檫@意味著你可以配置JdbcTemplate
的單個(gè)實(shí)例,然后將該共享引用安全地注入到多個(gè)DAO(或存儲(chǔ)庫(kù))中。JdbcTemplate
是有狀態(tài)的,因?yàn)樗S護(hù)對(duì)DataSource
的引用,但是此狀態(tài)不是會(huì)話狀態(tài)。
使用JdbcTemplate
類(和關(guān)聯(lián)的NamedParameterJdbcTemplate
類)的常見做法是在Spring配置文件中配置DataSource
,然后將共享的DataSource
bean依賴注入到DAO類中。 JdbcTemplate
在數(shù)據(jù)源的設(shè)置器中創(chuàng)建。這導(dǎo)致類似于以下內(nèi)容的DAO:
public class JdbcCorporateEventDao implements CorporateEventDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } // JDBC-backed implementations of the methods on the CorporateEventDao follow... }
以下示例顯示了相應(yīng)的XML配置:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="dataSource" class="org.apache.commons.dbcp.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> <context:property-placeholder location="jdbc.properties"/> </beans>
顯式配置的替代方法是使用組件掃描和注解支持進(jìn)行依賴項(xiàng)注入。在這種情況下,可以使用@Repository
注釋該類(這使其成為組件掃描的候選對(duì)象),并使用@Autowired
注解DataSource
setter方法。以下示例顯示了如何執(zhí)行此操作:
@Repository //1 public class JdbcCorporateEventDao implements CorporateEventDao { private JdbcTemplate jdbcTemplate; @Autowired //2 public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); //3 } // JDBC-backed implementations of the methods on the CorporateEventDao follow... }
用@Repository
注釋類。
用@Autowired
注釋DataSource
setter方法。
使用DataSource
創(chuàng)建一個(gè)新的JdbcTemplate
。
以下示例顯示了相應(yīng)的XML配置:
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- Scans within the base package of the application for @Component classes to configure as beans --> <context:component-scan base-package="org.springframework.docs.test" /> <bean id="dataSource" class="org.apache.commons.dbcp.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> <context:property-placeholder location="jdbc.properties"/> </beans>
如果你使用Spring的JdbcDaoSupport
類,并且從中擴(kuò)展了各種JDBC支持的DAO類,則你的子類將從JdbcDaoSupport
類繼承一個(gè)setDataSource(..)
方法。你可以選擇是否從此類繼承。提供JdbcDaoSupport
類目的只是為了方便。
無論你選擇使用(或不使用)以上哪種模板初始化樣式,都無需在每次要運(yùn)行SQL時(shí)都創(chuàng)建JdbcTemplate
類的新實(shí)例。配置完成后,JdbcTemplate
實(shí)例是線程安全的。如果你的應(yīng)用程序訪問多個(gè)數(shù)據(jù)庫(kù),你可能需要多個(gè)JdbcTemplate
實(shí)例,這需要多個(gè)數(shù)據(jù)源,然后需要多個(gè)不同配置的JdbcTemplate
實(shí)例。
參考代碼:
org.liyong.dataaccess.starter.JdbcTemplateBestTransactionManagerIocContainer
NamedParameterJdbcTemplate
與僅使用經(jīng)典占位符(?
)編程的JDBC語(yǔ)句相反,NamedParameterJdbcTemplate
類增加了使用命名參數(shù)對(duì)JDBC語(yǔ)句進(jìn)行編程的支持。NamedParameterJdbcTemplate
類包裝JdbcTemplate
并將其委托給包裝的JdbcTemplate
以完成其大部分工作。本節(jié)僅描述NamedParameterJdbcTemplate
類的那些與JdbcTemplate
本身不同的部分,通過使用命名參數(shù)對(duì)JDBC語(yǔ)句進(jìn)行編程。下面的示例演示如何使用NamedParameterJdbcTemplate
:
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActorsByFirstName(String firstName) { String sql = "select count(*) from T_ACTOR where first_name = :first_name"; SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
請(qǐng)注意,在分配給sql變量的值以及插入到namedParameters
變量(MapSqlParameterSource
類型)中的相應(yīng)值中使用了命名參數(shù)符號(hào)。
或者,你可以使用基于Map的格式將命名參數(shù)及其對(duì)應(yīng)的值傳遞給NamedParameterJdbcTemplate
實(shí)例。由NamedParameterJdbcOperations
暴露并由NamedParameterJdbcTemplate
類實(shí)現(xiàn)的其余方法遵循類似的模式,此處不再贅述。
以下示例說明了基于Map的格式的使用:
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActorsByFirstName(String firstName) { String sql = "select count(*) from T_ACTOR where first_name = :first_name"; Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
SqlParameterSource
接口是與NamedParameterJdbcTemplate
相關(guān)的一個(gè)不錯(cuò)的功能(并且存在于同一Java包中)。你已經(jīng)在前面的代碼片段之一(MapSqlParameterSource
類)中看到了此接口的實(shí)現(xiàn)示例。SqlParameterSource
是NamedParameterJdbcTemplate
的命名參數(shù)值的源。MapSqlParameterSource
類是一個(gè)簡(jiǎn)單的實(shí)現(xiàn),它是圍繞java.util.Map
的適配器,其中鍵是參數(shù)名稱、值是參數(shù)值。
另一個(gè)SqlParameterSource
實(shí)現(xiàn)是BeanPropertySqlParameterSource
類。此類包裝一個(gè)任意的JavaBean(即,遵循JavaBean約定的類的實(shí)例),并使用包裝的JavaBean的屬性作為命名參數(shù)值的源。
以下示例顯示了典型的JavaBean:
public class Actor { private Long id; private String firstName; private String lastName; public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public Long getId() { return this.id; } // setters omitted... }
以下示例使用NamedParameterJdbcTemplate
返回上一示例中顯示的類的成員數(shù):
// some JDBC-backed DAO class... private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int countOfActors(Actor exampleActor) { // notice how the named parameters match the properties of the above 'Actor' class String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"; SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); }
記住,NamedParameterJdbcTemplate
類包裝了經(jīng)典的JdbcTemplate
模板。如果需要訪問包裝的JdbcTemplate
實(shí)例以訪問僅在JdbcTemplate
類中提供的功能,則可以使用getJdbcOperations()
方法通過JdbcOperations
接口訪問包裝的JdbcTemplate
。
另請(qǐng)參閱JdbcTemplate最佳實(shí)踐,以獲取有關(guān)在應(yīng)用程序上下文中使用NamedParameterJdbcTemplate
類的指導(dǎo)。
參考代碼:
org.liyong.dataaccess.starter.NamedParameterTransactionManagerIocContainer
SQLExceptionTranslator
SQLExceptionTranslator
是由可以在SQLExceptions
和Spring自己的org.springframework.dao.DataAccessException
之間進(jìn)行轉(zhuǎn)換的類實(shí)現(xiàn)的接口,該類與數(shù)據(jù)訪問策略無關(guān)。為了提高精度,實(shí)現(xiàn)可以是通用的(例如,使用SQLState
代碼用于JDBC)或?qū)S械模ɡ?,使用Oracle錯(cuò)誤代碼)。
SQLErrorCodeSQLExceptionTranslator
是默認(rèn)使用的SQLExceptionTranslator
的實(shí)現(xiàn)。此實(shí)現(xiàn)使用特定的供應(yīng)商代碼。它比SQLState
實(shí)現(xiàn)更精確。錯(cuò)誤代碼的轉(zhuǎn)換基于一個(gè)名為SQLErrorCodes
的JavaBean類型類中的代碼。此類由SQLErrorCodesFactory
創(chuàng)建和填充,SQLErrorCodesFactory
是工廠,用于基于名為sql-error-codes.xml
的配置文件的內(nèi)容創(chuàng)建SQLErrorCodes
。此文件使用供應(yīng)商代碼填充,并基于從DatabaseMetaData
中獲取的DatabaseProductName
填充。使用你正在使用的實(shí)際數(shù)據(jù)庫(kù)的代碼。
SQLErrorCodeSQLExceptionTranslator
按以下順序應(yīng)用匹配規(guī)則:
子類實(shí)現(xiàn)的任何自定義轉(zhuǎn)換。通常,將使用提供的具體SQLErrorCodeSQLExceptionTranslator
,因此此規(guī)則不適用。僅當(dāng)你確實(shí)提供了子類實(shí)現(xiàn)時(shí),它才適用。
作為SQLErrorCodes
類的customSqlExceptionTranslator
屬性提供的SQLExceptionTranslator
接口的任何自定義實(shí)現(xiàn)。
搜索CustomSQLErrorCodesTranslation
類的實(shí)例列表(為SQLErrorCodes
類的customTranslations
屬性提供),以查找匹配項(xiàng)。
錯(cuò)誤代碼匹配被應(yīng)用。
使用后備轉(zhuǎn)換器。 SQLExceptionSubclassTranslator
是默認(rèn)的后備轉(zhuǎn)換器。如果此轉(zhuǎn)換器不可用,則下一個(gè)后備轉(zhuǎn)換器是SQLStateSQLExceptionTranslator
。
默認(rèn)情況下,使用
SQLErrorCodesFactory
定義錯(cuò)誤代碼和自定義異常轉(zhuǎn)換。從類路徑的名為sql-error-codes.xml
的文件中查找它們,并根據(jù)使用中數(shù)據(jù)庫(kù)的數(shù)據(jù)庫(kù)元數(shù)據(jù)中的數(shù)據(jù)庫(kù)名稱找到匹配的SQLErrorCodes
實(shí)例。
你可以擴(kuò)展SQLErrorCodeSQLExceptionTranslator
,如以下示例所示:
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { if (sqlEx.getErrorCode() == -12345) { return new DeadlockLoserDataAccessException(task, sqlEx); } return null; } }
在前面的示例中,特定的錯(cuò)誤代碼(-12345)被轉(zhuǎn)換,而其他錯(cuò)誤則由默認(rèn)轉(zhuǎn)換器實(shí)現(xiàn)轉(zhuǎn)換。要使用此自定義轉(zhuǎn)換器,必須通過setExceptionTranslator
方法將其傳遞給JdbcTemplate
,并且必須在需要此轉(zhuǎn)換器的所有數(shù)據(jù)訪問處理中使用此JdbcTemplate
。以下示例顯示了如何使用此自定義轉(zhuǎn)換器:
private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { // create a JdbcTemplate and set data source this.jdbcTemplate = new JdbcTemplate(); this.jdbcTemplate.setDataSource(dataSource); // create a custom translator and set the DataSource for the default translation lookup CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator(); tr.setDataSource(dataSource); this.jdbcTemplate.setExceptionTranslator(tr); } public void updateShippingCharge(long orderId, long pct) { // use the prepared JdbcTemplate for this update this.jdbcTemplate.update("update orders" + " set shipping_charge = shipping_charge * ? / 100" + " where id = ?", pct, orderId); }
定制轉(zhuǎn)換器會(huì)傳遞一個(gè)數(shù)據(jù)源,以便在sql-error-codes.xml
中查找錯(cuò)誤代碼。
運(yùn)行SQL語(yǔ)句需要很少的代碼。你需要一個(gè)數(shù)據(jù)源和一個(gè)JdbcTemplate,包括JdbcTemplate提供的便捷方法。下面的示例顯示了創(chuàng)建一個(gè)新表的最小但功能齊全的類需要包含的內(nèi)容:
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAStatement { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public void doExecute() { this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))"); } }
一些查詢方法返回單個(gè)值。要從一行中檢索計(jì)數(shù)或特定值,請(qǐng)使用queryForObject(..)。后者將返回的JDBC Type
轉(zhuǎn)換為作為參數(shù)傳遞的Java類。如果類型轉(zhuǎn)換無效,則拋出InvalidDataAccessApiUsageException。以下示例包含兩種查詢方法,一種用于int,另一種用于查詢String:
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class RunAQuery { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int getCount() { return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class); } public String getName() { return this.jdbcTemplate.queryForObject("select name from mytable", String.class); } }
除了單個(gè)結(jié)果查詢方法外,還有幾種方法返回一個(gè)列表,其中包含查詢返回的每一行的條目。最通用的方法是queryForList(..),它使用列名作為鍵,返回一個(gè)List,其中每個(gè)元素是一個(gè)Map,其中每個(gè)列包含一個(gè)條目。如果在前面的示例中添加一種方法來檢索所有行的列表,則可能如下所示:
private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public List<Map<String, Object>> getList() { return this.jdbcTemplate.queryForList("select * from mytable"); }
返回的列表類似于以下內(nèi)容:
[{name=Bob, id=1}, {name=Mary, id=2}]
下面的示例更新某個(gè)主鍵的列:
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAnUpdate { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public void setName(int id, String name) { this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id); } }
在前面的示例中,SQL語(yǔ)句具有用于行參數(shù)的占位符。你可以將參數(shù)值作為可變參數(shù)或作為對(duì)象數(shù)組傳遞。因此,你應(yīng)該在基本包裝器類中顯式包裝基類型,或者應(yīng)該使用自動(dòng)裝箱。
update()便捷方法支持檢索由數(shù)據(jù)庫(kù)生成的主鍵。此支持是JDBC 3.0標(biāo)準(zhǔn)的一部分。有關(guān)詳細(xì)信息,請(qǐng)參見規(guī)范的第13.6章。該方法將PreparedStatementCreator作為其第一個(gè)參數(shù),這是指定所需插入語(yǔ)句的方式。另一個(gè)參數(shù)是KeyHolder,它包含從更新成功返回時(shí)生成的主鍵。沒有標(biāo)準(zhǔn)的單一方法來創(chuàng)建適當(dāng)?shù)腜reparedStatement(這說明了為什么方法簽名就是這樣)。以下示例在Oracle上有效,但在其他平臺(tái)上可能不適用:
final String INSERT_SQL = "insert into my_test (name) values(?)"; final String name = "Rob"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" }); ps.setString(1, name); return ps; }, keyHolder); // keyHolder.getKey() now contains the generated key
本節(jié)內(nèi)容包括:
使用 DataSource
使用 DataSourceUtils
實(shí)現(xiàn) SmartDataSource
擴(kuò)張 AbstractDataSource
使用 SingleConnectionDataSource
使用 DriverManagerDataSource
使用 TransactionAwareDataSourceProxy
使用 DataSourceTransactionManager
DataSource
Spring通過DataSource獲得與數(shù)據(jù)庫(kù)的連接。DataSource是JDBC規(guī)范的一部分,是通用的連接工廠。它使容器或框架可以從應(yīng)用程序代碼中隱藏連接池和事務(wù)管理問題。作為開發(fā)人員,你無需了解有關(guān)如何連接到數(shù)據(jù)庫(kù)的詳細(xì)信息。這是設(shè)置數(shù)據(jù)源的管理員的責(zé)任。你很可能在開發(fā)和測(cè)試代碼時(shí)同時(shí)擔(dān)當(dāng)這兩個(gè)角色,但是你不必知道如何配置生產(chǎn)數(shù)據(jù)源。
使用Spring的JDBC層時(shí),你可以從JNDI獲取數(shù)據(jù)源,也可以使用第三方提供的連接池實(shí)現(xiàn)來配置自己的數(shù)據(jù)源。傳統(tǒng)的選擇是帶有bean樣式的DataSource類的Apache Commons DBCP和C3P0。對(duì)于現(xiàn)代JDBC連接池,請(qǐng)考慮使用具有其生成器樣式的API的HikariCP。
你僅應(yīng)將DriverManagerDataSource和SimpleDriverDataSource類(包含在Spring發(fā)行版中)用于測(cè)試!當(dāng)發(fā)出多個(gè)連接請(qǐng)求時(shí),這些變體不提供緩沖池,并且性能不佳。
以下部分使用Spring的DriverManagerDataSource實(shí)現(xiàn)。稍后將介紹其他幾種DataSource變體。
要配置DriverManagerDataSource:
通常與JDBC連接一樣,獲得與DriverManagerDataSource的連接。
指定JDBC驅(qū)動(dòng)程序的標(biāo)準(zhǔn)類名,以便DriverManager可以加載驅(qū)動(dòng)程序類。
提供不同JDBC驅(qū)動(dòng)程序的URL。(請(qǐng)參閱驅(qū)動(dòng)程序的文檔以獲得正確的值。)
提供用戶名和密碼以連接到數(shù)據(jù)庫(kù)。
以下示例顯示了如何在Java中配置DriverManagerDataSource:
DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); dataSource.setUrl("jdbc:hsqldb:hsql://localhost:"); dataSource.setUsername("sa"); dataSource.setPassword("");
以下示例顯示了相應(yīng)的XML配置:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <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> <context:property-placeholder location="jdbc.properties"/>
接下來的兩個(gè)示例顯示了DBCP和C3P0的基本連接和配置。要了解更多有助于控制池功能的選項(xiàng),請(qǐng)參閱相應(yīng)連接池實(shí)現(xiàn)的產(chǎn)品文檔。
以下示例顯示了DBCP配置:
<bean id="dataSource" class="org.apache.commons.dbcp.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> <context:property-placeholder location="jdbc.properties"/>
以下示例顯示了C3P0配置:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/>
DataSourceUtils
SmartDataSource接口應(yīng)該由可以提供與關(guān)系數(shù)據(jù)庫(kù)的連接的類來實(shí)現(xiàn)。它擴(kuò)展了DataSource接口,以允許使用它的類查詢?cè)诮o定操作后是否應(yīng)關(guān)閉連接。當(dāng)你知道需要重用連接時(shí),這種用法非常有效。
AbstractDataSource
AbstractDataSource是Spring的DataSource實(shí)現(xiàn)的抽象基類。它實(shí)現(xiàn)了所有DataSource實(shí)現(xiàn)通用的代碼。如果編寫自己的DataSource實(shí)現(xiàn),則應(yīng)該擴(kuò)展AbstractDataSource類。
SingleConnectionDataSource
SingleConnectionDataSource類是SmartDataSource接口的實(shí)現(xiàn),該接口包裝了每次使用后都未關(guān)閉的單個(gè)Connection。這不是多線程功能。
如果任何客戶端代碼在假定建立池連接的情況下調(diào)用close(如使用持久性工具時(shí)),則應(yīng)將preventClose屬性設(shè)置為true。此設(shè)置返回一個(gè)封閉物理包裝的代理。請(qǐng)注意,你不能再將此對(duì)象轉(zhuǎn)換為本地Oracle Connection或類似對(duì)象。
SingleConnectionDataSource主要是一個(gè)測(cè)試類。SingleConnectionDataSource主要是一個(gè)測(cè)試類。例如,它結(jié)合簡(jiǎn)單的JNDI環(huán)境,可以在應(yīng)用服務(wù)器外部輕松測(cè)試代碼。與DriverManagerDataSource相比,它始終重用同一連接,避免了過多的物理連接創(chuàng)建。
DriverManagerDataSource
DriverManagerDataSource類是標(biāo)準(zhǔn)DataSource接口的實(shí)現(xiàn),該接口通過bean屬性配置純JDBC驅(qū)動(dòng)程序,并每次返回新的Connection。
此實(shí)現(xiàn)對(duì)于Java EE容器外部的測(cè)試和獨(dú)立環(huán)境很有用,可以作為Spring IoC容器中的DataSource bean或與簡(jiǎn)單的JNDI環(huán)境結(jié)合使用。假定使用池的Connection.close()調(diào)用將關(guān)閉連接,因此任何可識(shí)別DataSource的持久性代碼都應(yīng)起作用。但是,即使在測(cè)試環(huán)境中,使用JavaBean風(fēng)格的連接池(例如commons-dbcp)也是如此容易,以至于總是在DriverManagerDataSource上使用這樣的連接池。
TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy是目標(biāo)DataSource的代理。代理包裝該目標(biāo)DataSource以增加對(duì)Spring管理的事務(wù)的意識(shí)。在這方面,它類似于Java EE服務(wù)器提供的事務(wù)性JNDI數(shù)據(jù)源。
除非需要調(diào)用已經(jīng)存在的代碼并通過標(biāo)準(zhǔn)的JDBC DataSource接口實(shí)現(xiàn),否則很少需要使用此類。在這種情況下,你仍然可以使該代碼可用,同時(shí)使該代碼參與Spring管理的事務(wù)。通常,最好使用更高級(jí)別的資源管理抽象來編寫自己的新代碼,例如JdbcTemplate或DataSourceUtils。
有關(guān)更多詳細(xì)信息,請(qǐng)參見TransactionAwareDataSourceProxy javadoc。
DataSourceTransactionManager
DataSourceTransactionManager類是單個(gè)JDBC數(shù)據(jù)源的PlatformTransactionManager實(shí)現(xiàn)。它將JDBC連接從指定的數(shù)據(jù)源綁定到當(dāng)前正在執(zhí)行的線程,可能允許每個(gè)數(shù)據(jù)源一個(gè)線程連接。
通過DataSourceUtils.getConnection(DataSource)而不是Java EE的標(biāo)準(zhǔn)DataSource.getConnection檢索JDBC連接需要應(yīng)用程序代碼。它拋出未檢查的org.springframework.dao異常,而不是經(jīng)過檢查的SQLException。所有框架類(例如JdbcTemplate)都隱式使用此策略。如果不與該事務(wù)管理器一起使用,則查找策略的行為與普通策略完全相同。因此,可以在任何情況下使用它。
DataSourceTransactionManager類支持自定義隔離級(jí)別和超時(shí),這些隔離級(jí)別和超時(shí)將應(yīng)用于適當(dāng)?shù)腏DBC語(yǔ)句查詢超時(shí)。為了支持后者,應(yīng)用程序代碼必須使用JdbcTemplate或?yàn)槊總€(gè)創(chuàng)建的語(yǔ)句調(diào)用DataSourceUtils.applyTransactionTimeout(..)方法。
在單資源情況下,可以使用此實(shí)現(xiàn)而不是JtaTransactionManager,因?yàn)樗恍枰萜髦С諮TA。只要遵循所需的連接查找模式,就可以在兩者之間進(jìn)行切換只是配置問題。JTA不支持自定義隔離級(jí)別。
如果將多個(gè)調(diào)用批處理到同一條準(zhǔn)備好的語(yǔ)句,則大多數(shù)JDBC驅(qū)動(dòng)程序都會(huì)提高性能。通過將更新分組成批,可以限制到數(shù)據(jù)庫(kù)的往返次數(shù)。
通過實(shí)現(xiàn)特殊接口的兩個(gè)方法BatchPreparedStatementSetter并將該實(shí)現(xiàn)作為batchUpdate方法調(diào)用中的第二個(gè)參數(shù)傳入,可以完成JdbcTemplate批處理。你可以使用getBatchSize方法提供當(dāng)前批處理的大小。你可以使用setValues方法設(shè)置語(yǔ)句的參數(shù)值。此方法稱為你在getBatchSize調(diào)用中指定的次數(shù)。以下示例根據(jù)列表中的條目更新t_actor表,并將整個(gè)列表用作批處理:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[] batchUpdate(final List<Actor> actors) { return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) throws SQLException { Actor actor = actors.get(i); ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); ps.setLong(3, actor.getId().longValue()); } public int getBatchSize() { return actors.size(); } }); } // ... additional methods }
如果處理更新流或從文件讀取,則可能具有首選的批處理大小,但最后一批可能沒有該數(shù)量的條目(譯者:意思是最后一批數(shù)據(jù)可能沒有分割數(shù)量大)。在這種情況下,可以使用InterruptibleBatchPreparedStatementSetter接口,該接口可在輸入源耗盡后中斷批處理(譯者:意思是數(shù)據(jù)源數(shù)據(jù)消耗完)。isBatchExhausted方法使你可以發(fā)出批處理結(jié)束的信號(hào)。
JdbcTemplate和NamedParameterJdbcTemplate都提供了另一種提供批處理更新的方式。無需實(shí)現(xiàn)特殊的批處理接口,而是將調(diào)用中的所有參數(shù)值作為列表提供??蚣苎h(huán)這些值,并使用一個(gè)內(nèi)部語(yǔ)句setter。API會(huì)有所不同,具體取決于你是否使用命名參數(shù)。對(duì)于命名參數(shù),你提供一個(gè)SqlParameterSource數(shù)組,該批處理的每個(gè)成員都有一個(gè)條目。你可以使用SqlParameterSourceUtils.createBatch便捷方法創(chuàng)建此數(shù)組,傳入一個(gè)bean樣式的對(duì)象數(shù)組(帶有與參數(shù)相對(duì)應(yīng)的getter方法),字符串鍵Map實(shí)例(包含對(duì)應(yīng)的參數(shù)作為值),或者混合使用。
以下示例顯示使用命名參數(shù)的批處理更新:
public class JdbcActorDao implements ActorDao { private NamedParameterTemplate namedParameterJdbcTemplate; public void setDataSource(DataSource dataSource) { this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); } public int[] batchUpdate(List<Actor> actors) { return this.namedParameterJdbcTemplate.batchUpdate( "update t_actor set first_name = :firstName, last_name = :lastName where id = :id", SqlParameterSourceUtils.createBatch(actors)); } // ... additional methods }
對(duì)于使用經(jīng)典的SQL語(yǔ)句?
占位符,則傳入包含更新值的對(duì)象數(shù)組的列表。該對(duì)象數(shù)組在SQL語(yǔ)句中的每個(gè)占位符必須具有一個(gè)條目,并且它們的順序必須與SQL語(yǔ)句中定義的順序相同。
以下示例與前面的示例相同,不同之處在于它使用經(jīng)典的JDBC?
占位符:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[] batchUpdate(final List<Actor> actors) { List<Object[]> batch = new ArrayList<Object[]>(); for (Actor actor : actors) { Object[] values = new Object[] { actor.getFirstName(), actor.getLastName(), actor.getId()}; batch.add(values); } return this.jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", batch); } // ... additional methods }
我們前面介紹的所有批處理更新方法都返回一個(gè)int數(shù)組,其中包含每個(gè)批處理?xiàng)l目的受影響行數(shù)。此計(jì)數(shù)由JDBC驅(qū)動(dòng)程序報(bào)告。如果該計(jì)數(shù)不可用,則JDBC驅(qū)動(dòng)程序?qū)⒎祷刂?2。
在這種情況下,通過在基礎(chǔ)PreparedStatement上自動(dòng)設(shè)置值,需要從給定的Java類型派生每個(gè)值的對(duì)應(yīng)JDBC類型。盡管這通常效果很好,但存在潛在的問題(例如,包含Map的空值)。在這種情況下,Spring默認(rèn)情況下會(huì)調(diào)用ParameterMetaData.getParameterType,這對(duì)于JDBC驅(qū)動(dòng)程序可能會(huì)很昂貴。如果遇到性能問題,則應(yīng)使用最新的驅(qū)動(dòng)程序版本,并考慮將spring.jdbc.getParameterType.ignore屬性設(shè)置為true(作為JVM系統(tǒng)屬性或在類路徑根目錄中的spring.properties文件中)。如關(guān)于Oracle 12c(SPR-16139)的報(bào)道。
或者,你可以考慮通過
BatchPreparedStatementSetter
(如前所示),通過為基于“List <Object []>
的調(diào)用提供的顯式類型數(shù)組,通過在服務(wù)器上的“registerSqlType
調(diào)用來顯式指定相應(yīng)的JDBC類型。自定義“MapSqlParameterSource
實(shí)例,或者通過BeanPropertySqlParameterSource
實(shí)例從Java聲明的屬性類型中獲取SQL類型,即使對(duì)于null
值也是如此。
前面的批處理更新示例處理的批處理太大,以至于你想將它們分解成幾個(gè)較小的批處理。你可以通過多次調(diào)用batchUpdate方法來使用前面提到的方法來執(zhí)行此操作,但是現(xiàn)在有一個(gè)更方便的方法。除了SQL語(yǔ)句外,此方法還包含一個(gè)對(duì)象集合,該對(duì)象包含參數(shù),每個(gè)批處理要進(jìn)行的更新次數(shù)以及一個(gè)ParameterizedPreparedStatementSetter來設(shè)置準(zhǔn)備好的語(yǔ)句的參數(shù)值??蚣鼙闅v提供的值,并將更新調(diào)用分成指定大小的批處理。
以下示例顯示了使用100的批量大小的批量更新:
public class JdbcActorDao implements ActorDao { private JdbcTemplate jdbcTemplate; public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); } public int[][] batchUpdate(final Collection<Actor> actors) { int[][] updateCounts = jdbcTemplate.batchUpdate( "update t_actor set first_name = ?, last_name = ? where id = ?", actors, 100, (PreparedStatement ps, Actor actor) -> { ps.setString(1, actor.getFirstName()); ps.setString(2, actor.getLastName()); ps.setLong(3, actor.getId().longValue()); }); return updateCounts; } // ... additional methods }
此調(diào)用的批處理更新方法返回一個(gè)int數(shù)組,該數(shù)組包含每個(gè)批處理的數(shù)組條目以及每個(gè)更新受影響的行數(shù)的數(shù)組。頂層數(shù)組的長(zhǎng)度指示運(yùn)行的批處理數(shù)量,第二層樹脂的長(zhǎng)度指示該批處理中的更新數(shù)量。 每個(gè)批次中的更新數(shù)量應(yīng)該是為所有批次提供的批次大?。ㄗ詈笠粋€(gè)可能更少),這取決于所提供的更新對(duì)象的總數(shù)。每個(gè)更新語(yǔ)句的更新計(jì)數(shù)是JDBC驅(qū)動(dòng)程序報(bào)告的更新計(jì)數(shù)。如果該計(jì)數(shù)不可用,則JDBC驅(qū)動(dòng)程序?qū)⒎祷刂?2。
到此,關(guān)于“如何使用JDBC核心類控制進(jìn)行JDBC處理”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。