大型的系统,数据量上升到一定程度,都会选择对库表进行切分,会根据表记录的性质,进行分表、或者表分区处理。 最近在系统升级,库表重新设计过程中,就遇到了分表、分区情况。
分表的处理比较简单,根据一定的规则,识别分表编号, 对对应的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;
-
-
-
-
-
-
-
- @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;
- }
- 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的约束, 应用时可能存在问题, 建议寻找合适的解决方法。