?
? ????? PHP ??? ???? ??? ?? ??
SimpleJdbcInsert
類和SimpleJdbcCall
類主要利用了JDBC驅動所提供的數據庫元數據的一些特性來簡化數據庫操作配置。
這意味著你可以盡可能的簡化你的數據庫操作配置。當然,你可以可以將元數據處理的特性關閉,從而在你的代碼中詳細指定這些特性。
讓我們從SimpleJdbcInsert
類開始。我們將盡可能使用最少量的配置。SimpleJdbcInsert
類必須在數據訪問層的初始化方法中被初始化。
在這個例子中,初始化方法為setDataSource
方法。你無需繼承自SimpleJdbcInsert
類,只需要創(chuàng)建一個新的實例,并通過withTableName
方法設置table名稱。
這個類使用了“fluid”模式返回SimpleJdbcInsert
,從而你可以訪問到所有的可以進行配置的方法。在這個例子中,我們只使用了一個方法,稍后我們會看到更多的配置方法。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor"); } public void add(Actor actor) { Map<String, Object> parameters = new HashMap<String, Object>(3); parameters.put("id", actor.getId()); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); insertActor.execute(parameters); } // ... additional methods }
這個方法通過接收 java.utils.Map
作為它唯一的參數。
在這里需要重點注意的是,在Map中的所有的key,必須和數據庫中定義的列名完全匹配。這是因為我們是通過讀取元數據來構造實際的Insert語句的。
接下來,我們對于同樣的插入語句,我們并不傳入id,而是通過數據庫自動獲取主鍵的方式來創(chuàng)建新的Actor對象并插入數據庫。
當我們創(chuàng)建SimpleJdbcInsert
實例時, 我們不僅需要指定表名,同時我們通過usingGeneratedKeyColumns
方法指定需要數據庫自動生成主鍵的列名。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") .usingGeneratedKeyColumns("id"); } public void add(Actor actor) { Map<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
這樣我們可以看到與之前執(zhí)行的insert操作的不同之處在于,我們無需添加id到參數的Map中去,只需要調用executeReturningKey
方法。
這個方法將返回一個java.lang.Number
對象,我們可以使用這個對象來創(chuàng)建一個字符類型的實例作為我們的域模型的屬性。
有一點很重要的地方需要注意,我們無法依賴所有的數據庫來返回我們指定的Java類型,因為我們只知道這些對象的基類是java.lang.Number
。
如果你有聯(lián)合主鍵或者那些非數字類型的主鍵需要生成,那么你可以使用executeReturningKeyHolder
方法返回的KeyHolder
對象。
通過指定所使用的字段名稱,可以使SimpleJdbcInsert僅使用這些字段作為insert語句所使用的字段。這可以通過usingColumns
方法進行配置。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") .usingColumns("first_name", "last_name") .usingGeneratedKeyColumns("id"); } public void add(Actor actor) { Map<String, Object> parameters = new HashMap<String, Object>(2); parameters.put("first_name", actor.getFirstName()); parameters.put("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
執(zhí)行這樣的insert語句所使用的字段,與之前我們所依賴的數據庫元數據是一致的。
使用Map來指定參數值有時候工作得非常好,但是這并不是最簡單的使用方式。Spring提供了一些其他的SqlParameterSource
實現類來指定參數值。
我們首先可以看看BeanPropertySqlParameterSource
類,這是一個非常簡便的指定參數的實現類,只要你有一個符合JavaBean規(guī)范的類就行了。它將使用其中的getter方法來獲取參數值。
下面是一個例子:
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") .usingGeneratedKeyColumns("id"); } public void add(Actor actor) { SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
另外一個實現類:MapSqlParameterSource
也使用Map來指定參數,但是他另外提供了一個非常簡便的addValue
方法,可以被連續(xù)調用,來增加參數。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcInsert insertActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.insertActor = new SimpleJdbcInsert(dataSource) .withTableName("t_actor") .usingGeneratedKeyColumns("id"); } public void add(Actor actor) { SqlParameterSource parameters = new MapSqlParameterSource() .addValue("first_name", actor.getFirstName()) .addValue("last_name", actor.getLastName()); Number newId = insertActor.executeAndReturnKey(parameters); actor.setId(newId.longValue()); } // ... additional methods }
正如你看到的,配置是一樣的,區(qū)別只是切換了不同的提供參數的實現方式來執(zhí)行調用。
接下來我們把我們的關注點放在使用 SimpleJdbcCall
來進行存儲過程的調用上。
設計這個類的目的在于使得調用存儲過程盡可能簡單。它同樣利用的數據庫元數據的特性來查找傳入的參數和返回值。
這意味著你無需明確聲明那些參數。當然,如果你喜歡,可以依然聲明這些參數,尤其對于某些參數,你無法直接將他們映射到Java類上,例如ARRAY類型和STRUCT類型的參數。
在我們的第一個示例中,我們可以看到一個簡單的存儲過程調用,它僅僅返回VARCHAR和DATE類型。
這里,我特地為Actor類增加了一個birthDate的屬性,從而可以使得返回值擁有不同的數據類型。
這個存儲過程讀取actor的主鍵,并返回first_name,last_name,和birth_date字段作為返回值。
下面是這個存儲過程的源碼,它可以工作在MySQL數據庫上:
CREATE PROCEDURE read_actor ( IN in_id INTEGER, OUT out_first_name VARCHAR(100), OUT out_last_name VARCHAR(100), OUT out_birth_date DATE) BEGIN SELECT first_name, last_name, birth_date INTO out_first_name, out_last_name, out_birth_date FROM t_actor where id = in_id; END;
正如你看到的,這里有四個參數,其中一個是傳入的參數“in_id”,表示了Actor的主鍵,剩下的參數是作為讀取數據庫表中的數據所返回的返回值。
SimpleJdbcCall
的聲明與SimpleJdbcInsert
類似,你無需繼承這個類,而只需要在初始化方法中進行初始化。
在這里例子中,我們只需要指定存儲過程的名稱。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcCall procReadActor; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); this.procReadActor = new SimpleJdbcCall(dataSource) .withProcedureName("read_actor"); } public Actor readActor(Long id) { SqlParameterSource in = new MapSqlParameterSource() .addValue("in_id", id); Map out = procReadActor.execute(in); Actor actor = new Actor(); actor.setId(id); actor.setFirstName((String) out.get("out_first_name")); actor.setLastName((String) out.get("out_last_name")); actor.setBirthDate((Date) out.get("out_birth_date")); return actor; } // ... additional methods }
通過SimpleJdbcCall執(zhí)行存儲過程需要創(chuàng)建一個SqlParameterSource
的實現類來指定傳入的參數。
需要注意的是,傳入參數的名稱與存儲過程中定義的名稱必須保持一致。這里,我們無需保持一致,因為我們使用數據庫的元數據信息來決定我們需要什么樣的數據庫對象。
當然,你在源代碼中所指定的名稱可能和數據庫中完全不同,有的數據庫會把這些名稱全部轉化成大寫,而有些數據庫會把這些名稱轉化為小寫。
execute
方法接收傳入的參數,并返回一個Map作為返回值,這個Map包含所有在存儲過程中指定的參數名稱作為key。
在這個例子中,他們分別是out_first_name,out_last_name
和
out_birth_date
。
execute
方法的最后部分是使用存儲過程所返回的值創(chuàng)建一個新的Actor實例。
同樣的,這里我們將名稱與存儲過程中定義的名稱保持一致非常重要。在這個例子中,在返回的Map中所定義的key值和數據庫的存儲過程中定義的值一致。
你可能需要在這里指定Spring使用Jakarta Commons提供的CaseInsensitiveMap
。這樣做,你需要在創(chuàng)建你自己的JdbcTemplate
類時,設置setResultsMapCaseInsensitive
屬性為True。
然后,你將這個自定義的JdbcTemplate
傳入SimpleJdbcCall
的構造函數。當然,你需要把commons-collections.jar
加入到classpath中去。
下面是配置示例:
public class JdbcActorDao implements ActorDao { private SimpleJdbcCall procReadActor; public void setDataSource(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setResultsMapCaseInsensitive(true); this.procReadActor = new SimpleJdbcCall(jdbcTemplate) .withProcedureName("read_actor"); } // ... additional methods }
通過這樣的配置,你就可以無需擔心返回參數值的大小寫問題。
你已經看到如何通過元數據來簡化參數配置,但是你也可以明確地指定這些參數??梢栽趧?chuàng)建SimpleJdbcCall
時,通過使用declareParameters
方法來聲明參數。
這個方法接收一組SqlParameter
對象作為參數。我們可以參照下一個章節(jié),如何創(chuàng)建SqlParameter
。
我們可以有選擇性的顯示聲明一個、多個、甚至所有的參數。參數元數據在這里會被同時使用。
通過調用withoutProcedureColumnMetaDataAccess
方法,我們可以指定數據庫忽略所有的元數據處理并使用顯示聲明的參數。
另外一種情況是,其中的某些參數值具有默認的返回值,我們需要在返回值中指定這些返回值。為了實現這個特性,我們可以使用useInParameterNames
來指定一組需要被包含的參數名稱。
這是一個完整的聲明存儲過程調用的例子:
public class JdbcActorDao implements ActorDao { private SimpleJdbcCall procReadActor; public void setDataSource(DataSource dataSource) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setResultsMapCaseInsensitive(true); this.procReadActor = new SimpleJdbcCall(jdbcTemplate) .withProcedureName("read_actor") .withoutProcedureColumnMetaDataAccess() .useInParameterNames("in_id") .declareParameters( new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR), new SqlOutParameter("out_last_name", Types.VARCHAR), new SqlOutParameter("out_birth_date", Types.DATE) ); } // ... additional methods }
執(zhí)行和最終的返回處理是相同的,我們在這里只是明確聲明了參數類型,而不是依賴數據庫元數據特性。 這一點很重要,尤其對于那些數據庫并不支持元數據的情況。當前,我們支持元數據的特性的數據包含:Apache Derby、DB2、MySQL、 Microsoft SQL Server、Oracle和Sybase。我們同時對某些數據庫內置函數支持元數據特性:MySQL、Microsoft SQL Server和Oracle。
為SimpleJdbc類或者后續(xù)章節(jié)提到的RDBMS操作指定參數,你需要使用SqlParameter
或者他的子類。
你可以通過指定參數名稱以及對應的SQL類型并傳入構造函數作為參數來指定SqlParameter
,其中,SQL類型是java.sql.Types
中所定義的常量。
我們已經看到過類似的聲明:
new SqlParameter("in_id", Types.NUMERIC), new SqlOutParameter("out_first_name", Types.VARCHAR),
第一行的SqlParameter
定義了一個傳入參數。傳入參數可以在所有的存儲過程中使用,也可以在稍后章節(jié)中提到的SqlQuery
類及其子類中使用。
第二行SqlOutParameter
定義了一個返回值。它可以被存儲過程調用所使用。當然,還有一個SqlInOutParameter
類,可以用于輸入輸出參數。
也就是說,它既是一個傳入參數,也是一個返回值。
除了參數名稱和SQL類型,你還可以聲明一些其他額外的選項。對于傳入參數,你可以為numeric數據類型指定精度,或者對于特定的數據庫指定特殊類型。
對于返回值,你可以提供一個RowMapper
接口來處理所有從REF cursor返回的列。另外一個選項是指定一個SqlReturnType
類,從而可以定制返回值的處理方式。
內置函數的調用幾乎和存儲過程的調用是一樣的。唯一的不同在于,你需要聲明的是一個函數的名稱而不是存儲過程的名稱。
這可以通過withFunctionName
方法來完成。使用這個方法,表明你的調用是一個函數。你所指定的這個函數名稱將被作為調用對象。
同時有一個叫做executeFunction
的方法,將獲得特定的Java類型的函數調用的返回值。
此時,你無需通過返回的Map來獲取返回值。另外有一個類似的便捷方法executeObject
用于存儲過程,但是他只能處理單個返回值的情況。
下面的示例展示了一個叫做get_actor_name
的函數調用,返回actor的完整的名稱。
這個函數將工作在MySQL數據庫上。
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;
調用這個函數,我們依然在初始化方法中創(chuàng)建SimpleJdbcCall
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcCall funcGetActorName; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(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 }
被調用的函數返回一個String
類型。
期望通過調用存儲過程或者函數來返回ResultSet一直是一個問題。一些數據庫在JDBC結果處理中返回結果集,而另外一些數據庫則需要明確指定返回值的類型。
無論哪種方法,都需要在循環(huán)遍歷結果集時,做出一些額外的工作,從而處理每一條記錄。
通過SimpleJdbcCall
,你可以使用returningResultSet
方法,并定義一個RowMapper
的實現類來處理特定的返回值。
當結果集在返回結果處理過程中沒有被定義名稱時,返回的結果集必須與定義的RowMapper
的實現類指定的順序保持一致。
而指定的名字也會被用作返回結果集中的名稱。
在這個例子中,我們將使用一個存儲過程,它并不接收任何參數,返回t_actor表中的所有的行,下面是MySQL數據庫中的存儲過程源碼:
CREATE PROCEDURE read_all_actors() BEGIN SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a; END;
要調用這個存儲過程,我們需要定義一個RowMapper
的實現類。我們所使用的類遵循JavaBean的規(guī)范,所以我們可以使用ParameterizedBeanPropertyRowMapper
作為實現類。
通過將相應的class類作為參數傳入到newInstance方法中,我們可以創(chuàng)建這個實現類。
public class JdbcActorDao implements ActorDao { private SimpleJdbcTemplate simpleJdbcTemplate; private SimpleJdbcCall procReadAllActors; public void setDataSource(DataSource dataSource) { this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.setResultsMapCaseInsensitive(true); this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate) .withProcedureName("read_all_actors") .returningResultSet("actors", ParameterizedBeanPropertyRowMapper.newInstance(Actor.class)); } public List getActorsList() { Map m = procReadAllActors.execute(new HashMap<String, Object>(0)); return (List) m.get("actors"); } // ... additional methods }
這個函數調用傳入一個空的Map進入,因為這里不需要任何的參數傳入。而函數調用所返回的結果集將返回的是Actors列表。