java.sql.SQLException: Value '0000-00-00 00:00:00' can not be represented as java.sql.Timestamp
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:861)
at com.mysql.jdbc.ResultSetRow.getTimestampFast(ResultSetRow.java:947)
at com.mysql.jdbc.ResultSetImpl.getTimestampInternal(ResultSetImpl.java:5921)
jdbcUrl 声明 zeroDateTimeBehavior=convertToNull
即可。
jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=true&zeroDateTimeBehavior=convertToNull
根据上述的异常日志,可以直接找到异常的出处。
/**
* @see com.mysql.jdbc.ResultSetRow#getTimestampFast
*/
protected Timestamp getTimestampFast(...) throws SQLException {
try {
// 标识是否 '0000-00-00 00:00:00'
boolean allZeroTimestamp = true;
// 标识是否为时间格式, 时间是允许 '00:00:00'
boolean onlyTimePresent = false;
// check 数据格式
for (int i = 0; i < length; i++) {
byte b = timestampAsBytes[offset + i];
if (b == ' ' || b == '-' || b == '/') {
onlyTimePresent = false;
}
if (b != '0' && b != ' ' && b != ':' && b != '-' && b != '/' && b != '.') {
allZeroTimestamp = false;
break;
}
}
if (!onlyTimePresent && allZeroTimestamp) {
if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL.equals(conn.getZeroDateTimeBehavior())) {
// 如果连接指定 convertToNull,遇到 allZeroTimestamp 直接返回 null
return null;
} else if (ConnectionPropertiesImpl.ZERO_DATETIME_BEHAVIOR_EXCEPTION.equals(conn.getZeroDateTimeBehavior())) {
// 默认配置 exception, 遇到 allZeroTimestamp 就会抛出上述的异常
throw SQLError.createSQLException("Value '" + StringUtils.toString(timestampAsBytes) + "' can not be represented as java.sql.Timestamp",
SQLError.SQL_STATE_ILLEGAL_ARGUMENT, this.exceptionInterceptor);
}
// We're left with the case of 'round' to a date Java _can_ represent, which is '0001-01-01'.
return rs.fastTimestampCreate(null, 1, 1, 1, 0, 0, 0, 0);
}
}
}
连接所有的配置是以 com.mysql.jdbc.ConnectionPropertiesImpl.ConnectionProperty 体现的。包括 allowableValues defaultValue sinceVersion 关键信息。
private StringConnectionProperty zeroDateTimeBehavior = new StringConnectionProperty("zeroDateTimeBehavior", ZERO_DATETIME_BEHAVIOR_EXCEPTION,
new String[] { ZERO_DATETIME_BEHAVIOR_EXCEPTION, ZERO_DATETIME_BEHAVIOR_ROUND, ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL },
Messages.getString("ConnectionProperties.zeroDateTimeBehavior",
new Object[] { ZERO_DATETIME_BEHAVIOR_EXCEPTION, ZERO_DATETIME_BEHAVIOR_ROUND, ZERO_DATETIME_BEHAVIOR_CONVERT_TO_NULL }),
"3.1.4", MISC_CATEGORY, Integer.MIN_VALUE);
mysql driver 对于异常日期数据的处理,可以借鉴到应用程序的开发中。
首先,需要考虑到异常数据对程序的影响,如果无法继续,需要抛出异常;
然后,针对异常数据,提供声明容错的的方案;提升系统稳定性。
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class ,Object.class})})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// implement pre-processing if needed
Object returnObject = invocation.proceed();
// implement post-processing if needed
return returnObject;
}
}
Intercepts (mybatis 3.5.11 API)
支持四大组件的处理拦截,实际使用拦截器主要是 Executor。即上述的 @Intercepts.type
ParameterHandler
StatementHandler
Executor
ResultSetHandler
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
/**
* Executor 初始化,同时进行插件增强。
* @see org.apache.ibatis.session.Configuration#newExecutor()
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
Executor executor = new SimpleExecutor(this, transaction);
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 基于JDK Proxy, executor 是经过各个interceptor 层层代理后的结果
// 代理生成是通过 Plugin.wrap(Object target, Interceptor interceptor) 完成的
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
// 解析@Intercepts ,得到期望拦截的组件和方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 如果有匹配拦截器的组件和方法,那么就进行代理。 例如: Executor.update()
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 关键实现,关注target 和 interceptor
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 通过 interceptor 把参数暴露出去,代码增强在interceptor 完成。
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
/*
* Look up or generate the designated proxy class.
* Return the cached copy or create the proxy class via the ProxyClassFactory
* Generate the specified proxy class via ProxyGenerator
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
* InvocationHandler 作为构造入参,生成代理实例
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
return cons.newInstance(new Object[]{h});
}
}
One of the design goals of logback is to audit and debug complex distributed applications.
It lets the developer place information in a diagnostic context that can be subsequently retrieved by certain logback components.
Chapter 8: Mapped Diagnostic Context
Java 常用的日志工具,logback log4j slf4j 都支持MDC 特性。本文分析 logback MDC。
// MDC operations such as put() and get() affect only the MDC of the current thread, and the children of the current thread.
// Thus, there is no need for the developer to worry about thread-safety or synchronization when programming with the MDC because it handles these issues safely and transparently.
public class LogbackMDCAdapter implements MDCAdapter {
// The internal map is copied so as
// We wish to avoid unnecessarily copying of the map. To ensure
// efficient/timely copying, we have a variable keeping track of the last
// operation.
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
}
ShardingSphere-JDBC 支持分库分表和读写分离。
是一种Datasource 实现,通过托管原有的Datasource,根据策略实现请求分发/路由。本文关注读写分离的主要实现。
spring
shardingsphere
master-slave:
name: ds_ms
master-data-source-name: ds_master
slave-data-source-names:
- ds_slave0
- ds_slave1
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 4.0.0-RC2 之后版本 负载均衡策略配置方式 -->
<master-slave:load-balance-algorithm id="randomStrategy" type="RANDOM" />
<master-slave:data-source id="masterSlaveDataSource" master-data-source-name="ds_master" slave-data-source-names="ds_slave0, ds_slave1" strategy-ref="randomStrategy">
<master-slave:props>
<prop key="sql.show">true</prop>
<prop key="executor.size">10</prop>
</master-slave:props>
</master-slave:data-source>
</beans>
监控、动态配置等辅助功能,在开发和测试场景中,并不需要。但是会在spring 启动加载过程中拖慢启动速度,开发、自测效率也受影响。
方案:配置spring profile,排除这些配置文件。在开发环境中,只加载关注的配置。
<?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"
profile="!speed">
<!-- 开发环境不需要的配置 -->
<!-- 例如:缓存,连接redis io操作 -->
<!-- 例如:mq 消费,连接broker io操作 -->
</beans>
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
}
JVM 配置 -Dspring.profiles.active="speed,profile2"
Bean definition profiles provide a mechanism in the core container that allows for registration of different beans in different environments.
关键类:org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
/**
* 解析并注册beans 标签下声明的bean。 Since:3.1
*/
protected void doRegisterBeanDefinitions(Element root) {
if (this.delegate.isDefaultNamespace(root)) {
// "profile"
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
// profile 格式解析,支持分隔符",; "
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// 判断profile 是否激活,如果不符合快速失败,直接return
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 正常解析和注册 bean
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
}
注解可以在类和方法上使用。
// Spring condition match 特性实现选择性加载, Since:4.0
@Conditional(ProfileCondition.class)
public @interface Profile {
/**
* The set of profiles for which the annotated component should be registered.
*/
String[] value();
}
profile 思想应用广泛,常见的还有maven profile。
spring profile 提供一整套环境切换的方案,更加简化了工程治理。