大型的系统,数据量上升到一定程度,都会选择对库表进行切分,会根据表记录的性质,进行分表、或者表分区处理。 最近在系统升级,库表重新设计过程中,就遇到了分表、分区情况。 分表的处理比较简单,根据一定的规则,识别分表编号, 对对应的VO(Bean)进行处理即可。
对于分区,本来应该比分表更简单, 因为Oracle的分区,对用户是透明的,用户本可以不用理会,程序无需改造即可。 但为了提高数据库查询效率,在查询和修改的条件中,需要加上分区字段,这样oracle特有的优化技术就能识别分区条件,提高效率。
1. 问题情况
在电信行业中,数据往往根据用户的区域进行分区存储,现有表ClientInfo, 根据其对应的Region 字段进行列值分区,在对应的查询和修改过程中, where条件中需要region字段值,这样有助于提高查询速度。现在问题就是: 有的表分区,有的表不分区, 如何在改动系统最小情况下, 让系统支持分区字段。
2. 思路
在网上百度了一把,没有找到合适的解决方案, 有些推荐Google的Hibernate Shards 分区包,粗略的了解了下,不太适合。 其实目前的情况很简单,就是一般查询时,在Where条件后增加分区字段 而尽量不改动程序。 想过 Hibernate 事件监听,感觉太复杂, 最终,想到了一个觉得可行的方法, 将分区字段加入到 主键中。 理由:分区字段是非空的、查询更新时与主键一起出现, 觉得没有问题,就开始动手实验。
3. 实验
建表语句:
create table clientinfo (
userID NUMBER(14) not null,
REGION NUMBER(5) not null,
username VARCHAR2(16) not null,
age number(3) ,
address VARCHAR2(128) ,
CHANGEDATE DATE,
primary key (userid)
)PARTITION BY LIST(REGION)
(
PARTITION clientinfo100 VALUES (100),
PARTITION clientinfo101 VALUES (101),
PARTITION clientinfo240 VALUES (270)
);
java代码:
VO类 ClientInfoVO
package com.qingcaolin;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
/**
* region 库表实际非主键, 该表根据region分区,加入虚拟主键提高查询速度
*
* @author qingcaolin
*
*/
@Entity
@IdClass(ClientInfoVOPK.class)
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@Table(name = "CLIENTINFO")
public class ClientInfoVO {
@Id
@Column(name = "userId")
private Long userId;
@Id
@Column(name = "REGION")
private Long region;
@Column(name = "USERNAME")
private String userName;
@Column(name = "AGE")
private Long age;
@Column(name = "address")
private String address;
@Column(name = "CHANGEDATE")
private Date changedate;
public ClientInfoVO(){}
public ClientInfoVOPK getPk() {
ClientInfoVOPK pk = new ClientInfoVOPK();
pk.setUserId(userId);
pk.setRegion(region);
return pk;
}
PK类:package com.qingcaolin;
import java.io.Serializable;
public class ClientInfoVOPK implements Serializable {
private Long userId;
private Long region;
public ClientInfoVOPK(){
}
public int hashCode() {
return (int) userId.hashCode();
}
public boolean equals(Object obj) {
if (obj == this)
return true;
if (obj == null)
return false;
if (!(obj instanceof ClientInfoVOPK))
return false;
ClientInfoVOPK pk = (ClientInfoVOPK) obj;
return pk.userId == userId;
}
测试Main类:
package com.qingcaolin;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
public class TestMain {
public static void main(String[] args) {
SessionFactory sf = new AnnotationConfiguration().addAnnotatedClass(ClientInfoVO.class).configure()
.buildSessionFactory();
{//新增
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
ClientInfoVO infoVO = new ClientInfoVO();
infoVO.setUserId(223L);
infoVO.setRegion(270L);
infoVO.setAge(18L);
infoVO.setUserName("qingcaolin");
infoVO.setAddress("武汉市");
infoVO.setChangedate(new Date());
session.save(infoVO);
tx.commit();
session.close();
}
{ //查询
Session querySession = sf.openSession();
Transaction queryTx = querySession.beginTransaction();
ClientInfoVO queryVO = new ClientInfoVO();
queryVO.setUserId(223L);
queryVO.setRegion(270L);
queryVO=(ClientInfoVO) querySession.get(ClientInfoVO.class,queryVO.getPk());
System.out.println(queryVO.getUserId()+","+queryVO.getRegion()+","+queryVO.getAge()+","
+queryVO.getUserName()+","+queryVO.getAddress()+","+queryVO.getChangedate());
queryVO.setAge(28L);
queryVO.setUserName("qingcaolin---");
queryVO.setAddress("武汉市--");
queryVO.setChangedate(new Date());
querySession.update(queryVO);
queryTx.commit();
querySession.close();
}
}
}控制台打印的日志:
11-15 23:05:49 - Not binding factory to JNDI, no JNDI name configured
Hibernate: insert into CLIENTINFO (address, AGE, CHANGEDATE, USERNAME, region, userId) values (?, ?, ?, ?, ?, ?)
Hibernate: select clientinfo0_.region as region0_0_, clientinfo0_.userId as userId0_0_, clientinfo0_.address as address0_0_, clientinfo0_.AGE as AGE0_0_, clientinfo0_.CHANGEDATE as CHANGEDATE0_0_, clientinfo0_.USERNAME as USERNAME0_0_ from CLIENTINFO clientinfo0_
where clientinfo0_.region=? and clientinfo0_.userId=?
223,270,18,qingcaolin,武汉市,2012-11-15 23:05:49.0
Hibernate: update CLIENTINFO set address=?, AGE=?, CHANGEDATE=?, USERNAME=? where region=? and userId=?
根据日志显示, 在查询和更新的时候, region都自动的加入到where条件。 测试通过,方案可行。
4.缺陷
对于上面的方案,有个明显的缺陷就是, 将分区字段region加入到虚拟主键中, Hibernate会认为主键为联合主键, 联合主键 会有一些约束,例如,Hibernate无法为联合主键中的列 自动读取sequence赋值。
因此,上面的方案无法应用于主键为 sequence自动生成的表, 还是存在一定的缺陷。 在实际应用中,也遇到过,在我们的系统中,这种sequence为主键的表,一般为日志 或者 查询为手写hql情况,需要自己补充条件。
具体情况具体分析, 如果 您有更好的方案 或者其他问题,欢迎一起讨论。
5.总结
将分区字段虚拟的加入 主键, 能比较方便的解决Hibernate支持分区问题, 对于Oracle的列值,范围分区 能显著提高性能。受限于Hibernate 的多主键 对于sequence的约束, 应用时可能存在问题, 建议寻找合适的解决方法。
欢迎加入讨论。转载请写明出处。谢谢!!

6836

被折叠的 条评论
为什么被折叠?



