文章目錄
spring對JDBC只是進行了簡單的封裝,主要大範圍利用回調函數解耦,相對來說靈活性也比較高.但沒有針對數據庫一些特性進行處理
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"></property>
<!--<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>-->
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="initialSize" value="1"></property>
<property name="maxIdle" value="2"></property>
<property name="minIdle" value="1"></property>
</bean>
<bean id="userService" class="org.example.jdbc.UserServiceImpl">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
service
package org.example.jdbc;
import java.util.List;
public interface UserService {
public void save(User user);
List<User> getUsers();
User getUser();
public Long queryForObject();
}
package org.example.jdbc;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.Types;
import java.util.List;
public class UserServiceImpl implements UserService {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public void save(User user) {
jdbcTemplate.update("insert into t_user(`name`,passwd) values (?,?)",
new Object[]{user.getName(),user.getPasswd()},
new int[]{Types.VARCHAR,Types.VARCHAR});
}
@Override
public List<User> getUsers() {
return jdbcTemplate.query("select * from t_user",new UserRowMapper());
}
@Override
public User getUser() {
return jdbcTemplate.query("select * from t_user where id =4", rs -> {rs.next();
return new User(rs.getLong("id"), rs.getString("name"), rs.getString("passwd"));
});
}
@Override
public Long queryForObject() {
return jdbcTemplate.queryForObject("select id from t_user where id =4",Long.class);
}
}
rowmapper
package org.example.jdbc;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("id"), rs.getString("name"), rs.getString("passwd"));
}
}
PO
package org.example.jdbc;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String passwd;
}
update方法解析源碼
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException {
return update(sql, newArgTypePreparedStatementSetter(args, argTypes));
}
/**
*
* @param psc 封裝sql
* @param pss 封裝參數和參數類型
* @return
* @throws DataAccessException
*/
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
//設置PreparedStatement所需全部參數
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isTraceEnabled()) {
logger.trace("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
/**
* 獲取數據庫連接
* 設置PreparedStatement,以及用戶設定的輸入參數
* 利用回調函數執行
* 警告處理
* 數據庫資源處理
* @param psc a callback that creates a PreparedStatement given a Connection
* @param action a callback that specifies the action
* @param <T>
* @return
* @throws DataAccessException
*/
@Override
@Nullable
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
//獲取數據庫連接池
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
//應用用戶設定的輸入參數
applyStatementSettings(ps);
//利用回調函數賦值
T result = action.doInPreparedStatement(ps);
//警告處理
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
//釋放數據庫連接,避免當異常轉換器沒有被初始化的時候出現潛在的連接池死鎖,
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);//關閉資源
DataSourceUtils.releaseConnection(con, getDataSource());//釋放連接
}
}
獲取數據庫連接
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
}
catch (IllegalStateException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
}
}
/*
獲取連接,如果線程要求同步的話緩存去
*/
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//獲取緩存連接,只有當線程支持同步的時候纔有值
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
//計數
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);//簡單的獲取連接
//當前線程支持同步
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
//在事務中使用同一數據庫連接
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
//記錄數據庫連接
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
設置參數
protected void applyStatementSettings(Statement stmt) throws SQLException {
int fetchSize = getFetchSize();
if (fetchSize != -1) {
//setMaxRows設置獲取總條數的上限
//減少網絡交互,訪問ResultSet時,一次獲取多少數據
stmt.setFetchSize(fetchSize);
}
int maxRows = getMaxRows();
//設置ResultSet支持最大行數
if (maxRows != -1) {
stmt.setMaxRows(maxRows);
}
//設置查詢超時時間
DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());
}
回調函數執行
public void setValues(PreparedStatement ps) throws SQLException {
int parameterPosition = 1;
if (this.args != null && this.argTypes != null) {
//遍歷每一個參數以作類型匹配以及轉換
for (int i = 0; i < this.args.length; i++) {
Object arg = this.args[i];
//如果是集合類則需要進入集合類內部遞歸解析集合內部屬性
if (arg instanceof Collection && this.argTypes[i] != Types.ARRAY) {
Collection<?> entries = (Collection<?>) arg;
for (Object entry : entries) {
if (entry instanceof Object[]) {
Object[] valueArray = ((Object[]) entry);
for (Object argValue : valueArray) {
doSetValue(ps, parameterPosition, this.argTypes[i], argValue);
parameterPosition++;
}
}
else {
doSetValue(ps, parameterPosition, this.argTypes[i], entry);
parameterPosition++;
}
}
}
else {
//解析當前屬性
doSetValue(ps, parameterPosition, this.argTypes[i], arg);
parameterPosition++;
}
}
}
}
日誌處理
protected void handleWarnings(Statement stmt) throws SQLException {
//當設置忽略警告,只打印日誌
if (isIgnoreWarnings()) {
if (logger.isDebugEnabled()) {
//如果日誌開啓的情況下打印日誌
SQLWarning warningToLog = stmt.getWarnings();
while (warningToLog != null) {
logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +
warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");
warningToLog = warningToLog.getNextWarning();
}
}
}
else {
handleWarnings(stmt.getWarnings());
}
}
關閉資源
public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) {
try {
doReleaseConnection(con, dataSource);
}
catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
}
catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
if (con == null) {
return;
}
if (dataSource != null) {
/*
當前線程存在事務的情況下說明存在共用數據庫連接直接使用ConnectionHolder中的released方法進行連接數-1,而不是真正釋放。
*/
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && connectionEquals(conHolder, con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
doCloseConnection(con, dataSource);
}
query方法解析源碼
首先設置實體映射類
public class UserRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("id"), rs.getString("name"), rs.getString("passwd"));
}
}
public List<User> getUsers() {
return jdbcTemplate.query("select * from t_user",new UserRowMapper());
}
@Override
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
//將結果轉換成POJO
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
@Override
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
//因爲無參,所以無需prepareStatement進行預編譯
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
結果轉成所需類型,主要調用自定義的實體映射類
@Override
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
int rowNum = 0;
while (rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
queryForObject根據返回class類型返回值
@Override
@Nullable
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}
@Override
@Nullable
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper);
return DataAccessUtils.nullableSingleResult(results);
}
/*
獲取值,且轉換成匹配類型,如果沒有的話嘗試spring轉換
*/
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
// Validate column count.
//驗證返回結果數
ResultSetMetaData rsmd = rs.getMetaData();
int nrOfColumns = rsmd.getColumnCount();//獲取數據庫列數
if (nrOfColumns != 1) {//超過一列異常
throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
}
// Extract column value from JDBC ResultSet.
//抽取第一個結果進行處理,數據庫列結果轉換成指定java類型
Object result = getColumnValue(rs, 1, this.requiredType);
//提取的值不匹配,轉換
if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
// Extracted value does not match already: try to convert it.
try {
//轉換對應類型
return (T) convertValueToRequiredType(result, this.requiredType);
}
catch (IllegalArgumentException ex) {
throw new TypeMismatchDataAccessException(
"Type mismatch affecting row number " + rowNum + " and column type '" +
rsmd.getColumnTypeName(1) + "': " + ex.getMessage());
}
}
return (T) result;
}
protected Object getColumnValue(ResultSet rs, int index, @Nullable Class<?> requiredType) throws SQLException {
if (requiredType != null) {
//轉換成指定類型
return JdbcUtils.getResultSetValue(rs, index, requiredType);
}
else {
// No required type specified -> perform default extraction.
//沒有設置轉換類型,默認轉換
return getColumnValue(rs, index);
}
}
轉換指定類型
/*
普通轉換
枚舉處理
sql默認類庫處理
新增對時間的處理
*/
public static Object getResultSetValue(ResultSet rs, int index, @Nullable Class<?> requiredType) throws SQLException {
if (requiredType == null) {
return getResultSetValue(rs, index);
}
Object value;
// Explicitly extract typed value, as far as possible.
//普通轉換
if (String.class == requiredType) {
return rs.getString(index);
}
else if (boolean.class == requiredType || Boolean.class == requiredType) {
value = rs.getBoolean(index);
}
else if (byte.class == requiredType || Byte.class == requiredType) {
value = rs.getByte(index);
}
else if (short.class == requiredType || Short.class == requiredType) {
value = rs.getShort(index);
}
else if (int.class == requiredType || Integer.class == requiredType) {
value = rs.getInt(index);
}
else if (long.class == requiredType || Long.class == requiredType) {
value = rs.getLong(index);
}
else if (float.class == requiredType || Float.class == requiredType) {
value = rs.getFloat(index);
}
else if (double.class == requiredType || Double.class == requiredType ||
Number.class == requiredType) {
value = rs.getDouble(index);
}
else if (BigDecimal.class == requiredType) {
return rs.getBigDecimal(index);
}
else if (java.sql.Date.class == requiredType) {
return rs.getDate(index);
}
else if (java.sql.Time.class == requiredType) {
return rs.getTime(index);
}
else if (java.sql.Timestamp.class == requiredType || java.util.Date.class == requiredType) {
return rs.getTimestamp(index);
}
else if (byte[].class == requiredType) {
return rs.getBytes(index);
}
else if (Blob.class == requiredType) {
return rs.getBlob(index);
}
else if (Clob.class == requiredType) {
return rs.getClob(index);
}
//枚舉處理
else if (requiredType.isEnum()) {
// Enums can either be represented through a String or an enum index value:
// leave enum type conversion up to the caller (e.g. a ConversionService)
// but make sure that we return nothing other than a String or an Integer.
Object obj = rs.getObject(index);
if (obj instanceof String) {
return obj;
}
else if (obj instanceof Number) {
// Defensively convert any Number to an Integer (as needed by our
// ConversionService's IntegerToEnumConverterFactory) for use as index
return NumberUtils.convertNumberToTargetClass((Number) obj, Integer.class);
}
else {
// e.g. on Postgres: getObject returns a PGObject but we need a String
return rs.getString(index);
}
}
//交給sql類庫處理
else {
// Some unknown type desired -> rely on getObject.
try {
return rs.getObject(index, requiredType);
}
catch (AbstractMethodError err) {
logger.debug("JDBC driver does not implement JDBC 4.1 'getObject(int, Class)' method", err);
}
catch (SQLFeatureNotSupportedException ex) {
logger.debug("JDBC driver does not support JDBC 4.1 'getObject(int, Class)' method", ex);
}
catch (SQLException ex) {
logger.debug("JDBC driver has limited support for JDBC 4.1 'getObject(int, Class)' method", ex);
}
// Corresponding SQL types for JSR-310 / Joda-Time types, left up
// to the caller to convert them (e.g. through a ConversionService).
//對時間的處理
String typeName = requiredType.getSimpleName();
if ("LocalDate".equals(typeName)) {
return rs.getDate(index);
}
else if ("LocalTime".equals(typeName)) {
return rs.getTime(index);
}
else if ("LocalDateTime".equals(typeName)) {
return rs.getTimestamp(index);
}
// Fall back to getObject without type specification, again
// left up to the caller to convert the value if necessary.
return getResultSetValue(rs, index);
}
// Perform was-null check if necessary (for results that the JDBC driver returns as primitives).
return (rs.wasNull() ? null : value);
}
默認類型處理
@Nullable
protected Object getColumnValue(ResultSet rs, int index) throws SQLException {
return JdbcUtils.getResultSetValue(rs, index);
}
/*
利用結果集返回的類型處理
*/
public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {
Object obj = rs.getObject(index);
String className = null;
if (obj != null) {
className = obj.getClass().getName();
}
if (obj instanceof Blob) {
Blob blob = (Blob) obj;
obj = blob.getBytes(1, (int) blob.length());
}
else if (obj instanceof Clob) {
Clob clob = (Clob) obj;
obj = clob.getSubString(1, (int) clob.length());
}
else if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) {
obj = rs.getTimestamp(index);
}
else if (className != null && className.startsWith("oracle.sql.DATE")) {
String metaDataClassName = rs.getMetaData().getColumnClassName(index);
if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
obj = rs.getTimestamp(index);
}
else {
obj = rs.getDate(index);
}
}
else if (obj instanceof java.sql.Date) {
if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
obj = rs.getTimestamp(index);
}
}
return obj;
}
提取的值與要求類型不匹配,轉換
/*
分別對string,number處理
如果處理不了,採用spring默認轉換器
*/
protected Object convertValueToRequiredType(Object value, Class<?> requiredType) {
//string處理
if (String.class == requiredType) {
return value.toString();
}
//對number處理
else if (Number.class.isAssignableFrom(requiredType)) {
if (value instanceof Number) {
// Convert original Number to target Number class.
//轉換原始Number=>Number類
return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType);
}
else {
// Convert stringified value to target Number class.
//轉換String類型的值到Number
return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType);
}
}
//使用spring自帶的轉換器轉換
else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) {
return this.conversionService.convert(value, requiredType);
}
else {
throw new IllegalArgumentException(
"Value [" + value + "] is of type [" + value.getClass().getName() +
"] and cannot be converted to required type [" + requiredType.getName() + "]");
}
}
spring默認轉換器
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}