Skip to content

Commit

Permalink
新增filter,用于处理MySQL jdbc升级导致的兼容性问题#5273
Browse files Browse the repository at this point in the history
新增filter用于处理MySQL jdbc升级导致的兼容性问题#5273
  • Loading branch information
lizongbo authored and wenshao committed May 13, 2023
1 parent 88a53c0 commit 2892a48
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package com.alibaba.druid.filter.mysql8datetime;

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;

/**
* 针对mysql jdbc 8.0.23及以上版本,通过该方法控制将对象类型转换成原来的类型
* @author lizongbo
* @see <a href="https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/news-8-0-24.html">...</a>
*/
public class MySQL8DateTimeResultSetMetaData implements ResultSetMetaData {
private ResultSetMetaData resultSetMetaData;

public MySQL8DateTimeResultSetMetaData(ResultSetMetaData resultSetMetaData) {
super();
this.resultSetMetaData = resultSetMetaData;
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return resultSetMetaData.unwrap(iface);
}

@Override
public int getColumnCount() throws SQLException {
return resultSetMetaData.getColumnCount();
}

@Override
public boolean isAutoIncrement(int column) throws SQLException {
return resultSetMetaData.isAutoIncrement(column);
}

@Override
public boolean isCaseSensitive(int column) throws SQLException {
return resultSetMetaData.isCaseSensitive(column);
}

@Override
public boolean isSearchable(int column) throws SQLException {
return resultSetMetaData.isSearchable(column);
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return resultSetMetaData.isWrapperFor(iface);
}

@Override
public boolean isCurrency(int column) throws SQLException {
return resultSetMetaData.isCurrency(column);
}

@Override
public int isNullable(int column) throws SQLException {
return resultSetMetaData.isNullable(column);
}

@Override
public boolean isSigned(int column) throws SQLException {
return resultSetMetaData.isSigned(column);
}

@Override
public int getColumnDisplaySize(int column) throws SQLException {
return resultSetMetaData.getColumnDisplaySize(column);
}

@Override
public String getColumnLabel(int column) throws SQLException {
return resultSetMetaData.getColumnLabel(column);
}

@Override
public String getColumnName(int column) throws SQLException {
return resultSetMetaData.getColumnName(column);
}

@Override
public String getSchemaName(int column) throws SQLException {
return resultSetMetaData.getSchemaName(column);
}

@Override
public int getPrecision(int column) throws SQLException {
return resultSetMetaData.getPrecision(column);
}

@Override
public int getScale(int column) throws SQLException {
return resultSetMetaData.getScale(column);
}

@Override
public String getTableName(int column) throws SQLException {
return resultSetMetaData.getTableName(column);
}

@Override
public String getCatalogName(int column) throws SQLException {
return resultSetMetaData.getCatalogName(column);
}

@Override
public int getColumnType(int column) throws SQLException {
return resultSetMetaData.getColumnType(column);
}

@Override
public String getColumnTypeName(int column) throws SQLException {
return resultSetMetaData.getColumnTypeName(column);
}

@Override
public boolean isReadOnly(int column) throws SQLException {
return resultSetMetaData.isReadOnly(column);
}

@Override
public boolean isWritable(int column) throws SQLException {
return resultSetMetaData.isWritable(column);
}

@Override
public boolean isDefinitelyWritable(int column) throws SQLException {
return resultSetMetaData.isDefinitelyWritable(column);
}

/**
* 针对8.0.24版本开始,如果把mysql DATETIME映射回Timestamp,就需要把javaClass的类型也改回去
* 相关类在com.mysql.cj.MysqlType 中
* 旧版本jdbc为
* DATETIME("DATETIME", Types.TIMESTAMP, Timestamp.class, 0, MysqlType.IS_NOT_DECIMAL, 26L, "[(fsp)]"),
* 8.0.24及以上版本jdbc实现改为
* DATETIME("DATETIME", Types.TIMESTAMP, LocalDateTime.class, 0, MysqlType.IS_NOT_DECIMAL, 26L, "[(fsp)]"),
* @param column 列的索引位
* @return
* @see java.sql.ResultSetMetaData#getColumnClassName(int)
* @throws SQLException
*/
@Override
public String getColumnClassName(int column) throws SQLException {
String className = resultSetMetaData.getColumnClassName(column);
if (LocalDateTime.class.getName().equals(className)) {
return Timestamp.class.getName();
}
return className;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.alibaba.druid.filter.mysql8datetime;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;

import com.alibaba.druid.filter.FilterAdapter;
import com.alibaba.druid.filter.FilterChain;
import com.alibaba.druid.proxy.jdbc.ResultSetProxy;

/**
* 针对mysql jdbc 8.0.23及以上版本,如果调用方没有使用orm框架,而是直接调用ResultSet的getObject方法,则针对DATETIME类型的字段,得到的对象从TimeStamp类型变成了LocalDateTime类型,导致调用方出现类型转换异常
* 通过Filter控制将对象类型转换成原来的类型
*
* @author lizongbo
* @see <a href="https://dev.mysql.com/doc/relnotes/connector-j/8.0/en/news-8-0-23.html">MySQL 8.0.23 更新说明</a>
*/
public class MySQL8DateTimeSqlTypeFilter extends FilterAdapter {

/**
* 针对mysql jdbc 8.0.23及以上版本,通过该方法控制将对象类型转换成原来的类型
* @param chain
* @param result
* @param columnIndex
* @return
* @see java.sql.ResultSet#getObject(int)
* @throws SQLException
*/
@Override
public Object resultSet_getObject(FilterChain chain, ResultSetProxy result, int columnIndex) throws SQLException {
return getObjectReplaceLocalDateTime(super.resultSet_getObject(chain, result, columnIndex));
}

/**
* 针对mysql jdbc 8.0.23及以上版本,通过该方法控制将对象类型转换成原来的类型
* @param chain
* @param result
* @param columnLabel
* @return
* @see java.sql.ResultSet#getObject(String)
* @throws SQLException
*/
@Override
public Object resultSet_getObject(FilterChain chain, ResultSetProxy result, String columnLabel) throws SQLException {
return getObjectReplaceLocalDateTime(super.resultSet_getObject(chain, result, columnLabel));
}

/**
* 针对mysql jdbc 8.0.23及以上版本,通过该方法控制将对象类型转换成原来的类型
*
* @param obj
* @return
*/
public static Object getObjectReplaceLocalDateTime(Object obj) {
if (!(obj instanceof LocalDateTime)) {
return obj;
}
// 针对升级到了mysql jdbc 8.0.23以上的情况,转换回老的兼容类型
return Timestamp.valueOf((LocalDateTime) obj);
}
}
1 change: 1 addition & 0 deletions core/src/main/resources/META-INF/druid-filter.properties
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
druid.filters.haRandomValidator=com.alibaba.druid.pool.ha.selector.RandomDataSourceValidateFilter
druid.filters.mysql8DateTime=com.alibaba.druid.filter.mysql8datetime.MySQL8DateTimeSqlTypeFilter
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.druid.bvt.filter;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;

import com.alibaba.druid.filter.mysql8datetime.MySQL8DateTimeSqlTypeFilter;
import com.alibaba.druid.mock.MockDriver;
import com.alibaba.druid.mock.MockPreparedStatement;
import com.alibaba.druid.mock.MockResultSet;
import com.alibaba.druid.mock.MockStatementBase;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.util.JdbcUtils;

import junit.framework.TestCase;
import java.sql.Timestamp;
import org.junit.Assert;

/**
* lizongbo
*/
public class MySQL8DateTimeSqlTypeFilterTest extends TestCase {

private DruidDataSource dataSource;

protected void setUp() throws Exception {
dataSource = new DruidDataSource();

dataSource.setUrl("jdbc:mock:xxx");
dataSource.setFilters("mysql8DateTime");

dataSource.setDriver(new MockDriver() {
public ResultSet createResultSet(MockPreparedStatement stmt) {
return new MyResultSet(stmt);
}

public ResultSet executeQuery(MockStatementBase stmt, String sql) throws SQLException {
return new MyResultSet(stmt);
}
});

dataSource.init();
}

protected void tearDown() throws Exception {
JdbcUtils.close(dataSource);
}

public void test_mysql8datetime() throws Exception {
Assert.assertTrue(dataSource.isInited());

MySQL8DateTimeSqlTypeFilter filter = (MySQL8DateTimeSqlTypeFilter) dataSource.getProxyFilters().get(0);

DruidPooledConnection conn = dataSource.getConnection();

final String PARAM_VALUE = "中国";
PreparedStatement stmt = conn.prepareStatement("select ?");
stmt.setString(1, PARAM_VALUE);

ResultSet rs = stmt.executeQuery();
MyResultSet rawRs = rs.unwrap(MyResultSet.class);

rs.next();
Object obj1 = rs.getObject(1);
System.out.println(obj1.getClass() + "|" + obj1);
Assert.assertEquals(Timestamp.class, obj1.getClass());
Object obj2 = rs.getObject("cc");
System.out.println(obj2.getClass() + "|" + obj2);
Assert.assertEquals(Timestamp.class, obj2.getClass());

rs.close();
stmt.close();

conn.close();

}

public static class MyResultSet extends MockResultSet {

public MyResultSet(Statement statement) {
super(statement);
}

@Override
public Object getObject(int index) throws SQLException {
return LocalDateTime.now();
}

@Override
public Object getObject(String columnLabel) throws SQLException {
return LocalDateTime.now();
}


}
}

0 comments on commit 2892a48

Please sign in to comment.