项目中使用了JPA Partition的方式进行分库,并没有采用阿里的那套读写分离(太繁琐、太繁琐...)。当下,最好的provider应该是hibernate,只可惜这项功能官方说是到5.0版本才能支持,碰巧eclipselink 2.5.0中实现了此功能,因此就选择了eclipse link。
其中的HashPartition说来也简单,就是把数据中涉及到分库字段的相关查询、持久化的操作指向到连接池的制定数据库上。说来应用起来也是比较简单。
@HashPartitioning(name="HashPartitionBySubId", partitionColumn=@Column(name="subId"), connectionPools={"default","node1"})
@Partitioned("HashPartitionBySubId")
public class SubEntity{
}
可能是使用正值数值做索引的缘故吧,2.5.0并没有注意到在做hash值得时候有可能出现负值的问题,因而会造成 ArrrayIndexBoundException 的错误。贴上代码一览:
org.eclipse.persistence.core-2.5.0.jar 包中 org.eclipse.persistence.descriptors.partitioning.HashPartitioningPolicy中:
/**
* INTERNAL:
* Get a connection from one of the pools in a round robin rotation fashion.
*/
public List<Accessor> getConnectionsForQuery(AbstractSession session, DatabaseQuery query, AbstractRecord arguments) {
Object value = arguments.get(this.partitionField);
if (value == null) {
if (this.unionUnpartitionableQueries) {
// Use all connections.
List<Accessor> accessors = new ArrayList<Accessor>(this.connectionPools.size());
for (String poolName : this.connectionPools) {
accessors.add(getAccessor(poolName, session, query, false));
}
return accessors;
} else {
// Use default behavior.
return null;
}
}
int index = value.hashCode() % this.connectionPools.size();
if (session.getPlatform().hasPartitioningCallback()) {
// UCP support.
session.getPlatform().getPartitioningCallback().setPartitionId(index);
return null;
}
// Use the mapped connection pool.
List<Accessor> accessors = new ArrayList<Accessor>(1);
String poolName = this.connectionPools.get(index);
accessors.add(getAccessor(poolName, session, query, false));
return accessors;
}
其中问题出在 varlue.hashCode()上,该内容可能为负值,通过mod计算后,出来的index成为负值,直接造成connectionPools.get(index)越界。
当下,好在2.5.2对这一BUG进行了修正。
/**
* INTERNAL:
* Get a connection from one of the pools in a round robin rotation fashion.
*/
public List<Accessor> getConnectionsForQuery(AbstractSession session, DatabaseQuery query, AbstractRecord arguments) {
Object value = arguments.get(this.partitionField);
if (value == null) {
if (this.unionUnpartitionableQueries) {
// Use all connections.
List<Accessor> accessors = new ArrayList<Accessor>(this.connectionPools.size());
for (String poolName : this.connectionPools) {
accessors.add(getAccessor(poolName, session, query, false));
}
return accessors;
} else {
// Use default behavior.
return null;
}
}
int index = Math.abs(value.hashCode()) % this.connectionPools.size();
if (session.getPlatform().hasPartitioningCallback()) {
// UCP support.
session.getPlatform().getPartitioningCallback().setPartitionId(index);
return null;
}
// Use the mapped connection pool.
List<Accessor> accessors = new ArrayList<Accessor>(1);
String poolName = this.connectionPools.get(index);
accessors.add(getAccessor(poolName, session, query, false));
return accessors;
}
所以如果使用JPA PARTITION功能的朋友,最好把版本提升到2.5.2.