使用NamedParameterJdbcTemplate向資料庫插入含有枚舉類型數據的對象時會出現的JAVA和SQL類型不匹配的情況,例如對於如下的JAVA類定義和對應的Oracle表定義: 執行如下代碼插入數據時出現了“無效的列類型”錯誤 從Oracle中讀取varchar2類型數據裝配到相應的枚 ...
使用NamedParameterJdbcTemplate向資料庫插入含有枚舉類型數據的對象時會出現的JAVA和SQL類型不匹配的情況,例如對於如下的JAVA類定義和對應的Oracle表定義:
1 public class MetaPhysicColumn { 2 3 public enum DataType{NUMBER, STRING, DATE, TIMESTAMP} 4 5 private String id; 6 private String name; 7 private String description; 8 private String tableId; 9 private DataType dataType; 10 private boolean primaryKey; 11 }
1 create table meta_physic_column 2 ( 3 id varchar2(128), 4 name varchar2(128), 5 description varchar2(512), 6 table_id varchar2(128), 7 data_type varchar2(128), 8 primary_key varchar2(1) 9 );
執行如下代碼插入數據時出現了“無效的列類型”錯誤
1 public void insertPhysicColumn(MetaPhysicColumn physicColumn) { 2 String sql = "insert into meta_physic_column (id, name, description, table_id, data_type, primary_key) values( :id, :name, :description, :tableId, :dataType, :primaryKey)"; 3 SqlParameterSource paramSource = new BeanPropertySqlParameterSource(physicColumn); 4 this.getNamedParameterJdbcTemplate().update(sql, paramSource); 5 }
org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [insert into cngtest(id, name, description, table_id, data_type, primary_key) values( ?, ?, ?, ?, ?, ?)]; SQL state [99999]; error code [17004]; 無效的列類型; nested exception is java.sql.SQLException: 無效的列類型 at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649)
從Oracle中讀取varchar2類型數據裝配到相應的枚舉類型不會出現問題,但是插入數據時,BeanPropertySqlParameterSource沒有提供枚舉類型到varchar2的映射,執行時會報錯。從源碼分析:SqlParameterSource是用來實現命名參數傳遞的介面,NamedParameterJdbcTemplate.update()通過調用其中的getSqlType(var)和getValue(var)兩個函數來獲取列對應的SQL類型和JAVA中綁定的對象,BeanPropertySqlParameterSource是SqlParameterSource的一個實現,繼承結構為BeanPropertySqlParameterSource extends AbstractSqlParameterSource implements SqlParameterSource,AbstractSqlParameterSource類中提供了registerSqlType()函數手動註冊列的類型,可以通過這個介面把枚舉對應列註冊為varchar2類型,代碼如下,但是每個對象中的枚舉屬性名字都不同,這個方法不具有一般性;
1 public void insertPhysicColumn(MetaPhysicColumn physicColumn) { 2 String sql = "insert into meta_physic_column (id, name, description, table_id, data_type, primary_key) 3 values( :id, :name, :description, :tableId, :dataType, :primaryKey)"; 4 BeanPropertySqlParameterSource paramSource = new BeanPropertySqlParameterSource(physicColumn); 5 paramSource.registerSqlType("dataType", Types.VARCHAR); 6 this.getNamedParameterJdbcTemplate().update(sql, paramSource); 7 }
另一個解決方法是繼承BeanPropertySqlParameterSource,在子類中覆蓋getSqlType()方法,其中先調用父類的getSqlType(),如果沒有找到列對應的SQL類型,並且這個列對應對象為枚舉類型,則返回Varchar2, 在使用上子類和BeanPropertySqlParameterSource完全一樣,但是提供了對枚舉類型的支持,代碼如下
1 public class MyBeanPropertySqlParameterSource extends BeanPropertySqlParameterSource { 2 3 public MyBeanPropertySqlParameterSource(Object object) { 4 super(object); 5 } 6 @Override 7 public int getSqlType(String var) { 8 int sqlType = super.getSqlType(var); 9 if (sqlType == TYPE_UNKNOWN && hasValue(var)) { 10 if (getValue(var).getClass().isEnum()) { 11 sqlType = Types.VARCHAR; 12 } 13 } 14 return sqlType; 15 } 16 }
1 public void insertPhysicColumn(MetaPhysicColumn physicColumn) { 2 String sql = "insert into meta_physic_column (id, name, description, table_id, data_type, primary_key) values( :id, :name, :description, :tableId, :dataType, :primaryKey)"; 3 SqlParameterSource paramSource = new MyBeanPropertySqlParameterSource(physicColumn); 4 this.getNamedParameterJdbcTemplate().update(sql, paramSource); 5 }
ps:對於boolean類型,BeanPropertySqlParameterSource將其映射為SQL的varchar2,將表結構對應類型設為number會報錯;從資料庫讀取varchar2類型到boolean類型時,JdbcTemplate只能識別0和1,其他字元同樣會報錯。