แก้ไขการ Generate ID จาก ORACLE Sequence มาเป็น เรียก ORACLE Function
แนะนำความต้องทำความรู้จัก Hibernate, Spring Framework เบื้องต้น ก็จะเป็นการดีครับผม
-
spring framework 3 quickstart
-
Spring Framework and Hibernate on Annotation
เรามาเข้าจุดที่เราสนใจกันจริง ๆ ดีกว่า ในส่วนของ Hibernate Map Class Model กับ Database นั้น
เราจะมีการเขียน Class ในลักษณะนี้ครับ
โดยในตัวอย่างแรกนี้ จะเป็นเรื่องการ Map ID ของ Class ให้มีการดึง Sequence Oracle
มาเป็น id ตอน insert ข้อมูล
Process.java
package x.y.z.projecttracking.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@Entity
@Table(name="PROCESS")
public class Process extends BaseEntity {
public Process(){}
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PROJ_GEN")
@SequenceGenerator(name = "SEQ_PROJ_GEN", sequenceName = "SEQ_PROJ")
private Long id;
@Column
private String code;
@Column
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
ในส่วนนี้จะเป็นเครื่องมือที่ Hibernate มีให้ใช้งานได้ทันที
ส่วนที่สำคัญที่มีผลต่อการ generate id ก็เป็นดังนี้
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PROJ_GEN")
ในส่วน GeneratedValue นี้จะบอกชนิดของการสร้าง id โดยระบุ strategy คือ ลักษณะการสร้าง id เป็น
Sequence Oracle
(NOTE : กรณี MySQL นั้น ไม่มี Sequence จะใช้แค่ @GeneratedValue เปล่า ๆ เลยแล้วทาง MySQL จะรู้เองว่า ต้องการขอ id ใหม่ ก็จะสร้างเลขใหม่ไม่ซ้ำให้ แต่ ORACLE ไม่มี auto generate เช่นนั้น โดยทั่วไปถึงต้องเรียกใช้ sequence ที่เป็นอีก function เฉพาะใน oracle ให้ทำการสร้าง running -number เพื่องานใด ๆ)
ส่วน generator นั้นก็จะเป็นตัวบอกว่าไปหา ตัวที่ทำการสร้างนั้นที่ใหน ด้วยชื่อ ตามด้านล่างนี้เลยครับ
@SequenceGenerator(name = "SEQ_PROJ_GEN", sequenceName = "SEQ_PROJ")
ในส่วน SequenceGenerator ตัวนี้ถือเป็นเครื่องมือในการดึงข้อมูลจาก Sequence ORACLE ที่เราได้เตรียมไว้แล้ว
โดย name ก็คือชื่อแทนตัว ไว้ให้สำหรับเรียกใช้จากที่อื่น
ส่วน sequenceName ก็จะเป็นตัวระบุว่า Sequence ORACLE ที่เราต้องการใช้งานนั้น มีชื่อว่าอะไร
จบในส่วน เกรินนำ ID Generate By Sequence ไว้เท่านี้ก่อน
เราไปเริ่มแก้ให้เป็น ID Generate By Function กันดีกว่า
เริ่มจาก เราเปลี่ยน Generator ใหม่เป็นดังนี้
Process.java
package x.y.z.projecttracking.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import org.hibernate.annotations.GenericGenerator;
@Entity
@Table(name="PROCESS")
public class Process extends BaseEntity {
public Process(){}
@Id
@GeneratedValue(generator = "projIdSeqGen")
@GenericGenerator(
name = "projIdSeqGen",
strategy = "x.y.z.projecttracking.generator.ProjIdSequenceGenerator")
private Long id;
@Column
private String code;
@Column
private String description;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
มีส่วนที่เปลี่ยนแปลง 2 ส่วน ได้แก่
@GeneratedValue(generator = "projIdSeqGen")
ในส่วน strategy เราไม่ต้องระบุแล้ว เพราะเราจะปรับแต่งเอาเอง โดยใช้ตัว genreator พื้นฐานคือ GenericGenerator
@GenericGenerator(
name = "projIdSeqGen",
strategy = "x.y.z.projecttracking.generator.ProjIdSequenceGenerator")
ในส่วนนี้เราใช้ GenericGenerator แล้วนำมาปรับแต่ง เพิ่มเติมโดย เราจะเห็นว่ามีการระบุ strategy ขึ้นมา
แล้วเราก็เขียน class ซึ่งจะมี code ตามด้านล่างนี้
package x.y.z.projecttracking.generator;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.CallableStatement;
import java.sql.Types;
import java.util.Properties;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.SequenceHiLoGenerator;
import org.hibernate.type.Type;
/**
*
* @author Kan.s
*/
public abstract class FunctionIdSequenceGenerator implements PersistentIdentifierGenerator, Configurable {
protected final SequenceHiLoGenerator delegate = new SequenceHiLoGenerator();
protected Constructor constructor;
@Override
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return delegate.sqlCreateStrings(dialect);
}
@Override
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
return delegate.sqlDropStrings(dialect);
}
@Override
public Object generatorKey() {
return delegate.generatorKey();
}
@Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
try {
String sqlCallFunc = "{ ? = call "+getFunctionName()+"() }";
System.out.println("SQL call Function() : " + sqlCallFunc);
CallableStatement call = session.connection().prepareCall(sqlCallFunc);
call.registerOutParameter(1, Types.VARCHAR);
call.execute();
String result = call.getString(1);
return typedIdForValue(result);
} catch (Exception e) {
throw new HibernateException("delegate didn't return a string", e);
}
}
@Override
public void configure(Type type, Properties params, Dialect d) throws MappingException {
delegate.configure(Hibernate.STRING, params, d);
try {
constructor = type.getReturnedClass().getConstructor(String.class);
} catch (NoSuchMethodException e) {
throw new HibernateException("FunctionIdSequenceGenerator is only applicable for types deriving from AbstractPersistenLongId", e);
}
}
protected Serializable typedIdForValue(String val) {
try {
return (Serializable) constructor.newInstance(val);
} catch (InstantiationException e) {
throw new HibernateException("Cannot create strongly typed id", e);
} catch (IllegalAccessException e) {
throw new HibernateException("Cannot create strongly typed id", e);
} catch (InvocationTargetException e) {
throw new HibernateException("Cannot create strongly typed id", e);
}
}
public abstract String getFunctionName();
}
class นี้ สร้างเป็น abstract เพื่อให้สามารถนำไปเรียกใช้ด้วย class อื่นได้ ไม่ได้ระบุว่า class นี้นำไปทำงานได้ทันทีเนื่องจากอยากให้สามารถใช้ประโยชน์ได้หลายครั้ง ไม่ต้องเขียนใหม่บ่อย
id ส่วนนี้ ผมจะ return String เลยจัดการแก้ในส่วน Method configure()
ส่วนสำคัญที่ทำหน้าที่สร้าง ID คือ Method generate ในส่วนนี้ผมได้แก้ให้เป็น เรียก Function ORACLE โดย Class ที่นำไปใช้งานจริง จะถูกบังคับให้สร้าง Method getFunctionName() เพื่อระบุชื่อ Function ORACLE ที่ต้องการทำการเรียกใช้งาน
หากต้องการใส่ parameter เข้าไปยัง function ก็ใช้ประมาณนี้ครับ
String sqlCallFunc = "{ ? = call "+getFunctionName()+"(?, ?) }";
String param1 = "test";
Long param2 = "test";
CallableStatement call = session.connection().prepareCall(sqlCallFunc);
call.registerOutParameter(1, Types.VARCHAR);
call.setString(2, param1);
call.setLong(3, param2 );
call.execute();
String result = call.getString(1);
ตามตัวอย่างนี้ก็หวังว่าจะนำไป apply ใช้ประโยชน์ได้นะครับ
ส่วน Class ที่นำไปใช้งานจริงก็เป็นแบบนี้ครับ
package x.y.z.projecttracking.generator;
/**
*
* @author Kan.s
*/
public class ProjIdSequenceGenerator extends FunctionIdSequenceGenerator {
@Override
public String getFunctionName() {
return "getProjId";
}
}
เรามาดูตัวอย่าง PL/SQL ที่ Generate function ORACLE กันบ้างดีกว่า
ในที่นี้เขียนง่าย ๆ เพราะเขียนได้แค่นี้ครับ T_T
ขั้นตอน คือ นำเอาวันเวลาปัจจุบัน มาแล้วเติม prefix เป็น 'PJ'
CREATE OR REPLACE FUNCTION getProjId(
) RETURN VARCHAR2 IS
dttm_curr VARCHAR2(20);
cursor c1 IS
SELECT TO_CHAR(CURRENT_DATE, 'DDMMYYYYHHMISSSSS') FROM DUAL;
BEGIN
open c1;
fetch c1 into dttm_curr;
close c1;
return CONCAT('PJ', dttm_curr);
END getId4DttmCurr ;
Result : PJXXXXXXXXXXXXXXXXX
X is number 0-9
กรณี เขียนแบบให้รับ parameter
CREATE OR REPLACE FUNCTION getProjId(prefix IN varchar2
) RETURN VARCHAR2 IS
dttm_curr VARCHAR2(20);
cursor c1 IS
SELECT TO_CHAR(CURRENT_DATE, 'DDMMYYYYHHMISSSSS') FROM DUAL;
BEGIN
open c1;
fetch c1 into dttm_curr;
close c1;
return CONCAT(prefix, dttm_curr);
END getId4DttmCurr ;
------------------------------------------------
Thank you for...
credit :
-
Custom Id Generators For Typed Id Values With Hibernate
-
stackoverflow : How to call a Oracle function from hibernate with return parameter?