You know that when you automatically generating table structure using JPA entities, the field order you defined in the entity class not going to be sync with correspondent generated table column order. If you are not dealing directly with databases (I mean using native sql) the order of columns won't be a big matter for you. But most of the time we have to go through the database records to fix various problems with the data. So in a time like that, column order will going to matter a lot for a clear and readable result set with native sql.
So how you going to define the column order easily ? When we search on this topic under Eclipse Link, we found following URL.
http://program.g.hatena.ne.jp/halflite/20130801/jpacolumnorder
Above link content is in Japanese. So it is little hard understand even after converting to the English :). So I thought what if I could do it in a better way (at least better for me). I thought it is better if we could define column order meta data within the same entity class (using annotation), so that it is readable for anyone.
With Eclipse Link you can customize the generating table column order by assigning a weight for each field. Please refer following link.
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Customizers#DescriptorCustomizer
Above does not having how to use weight property in entity class fields, but it contains the concept of extending defined meta data using the @Customizer annotation.
So now lets see how to do this.
Creating the Java Annotation to hold column order meta data
Creating the Java Annotation to hold column order meta data
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnPosition {
int position();
}
Above is the custom Java annotation to define the column order meta data in the JPA entity class. Using the position field you can specify the column position.
Eclipse Link Column Order Customize Annotation
Eclipse Link Column Order Customize Annotation
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.mappings.DatabaseMapping;
public class EntityColumnPositionCustomizer implements DescriptorCustomizer {
@Override
public void customize(ClassDescriptor descriptor) throws Exception {
descriptor.setShouldOrderMappings(true);
List<DatabaseMapping> mappings = descriptor.getMappings();
addWeight(this.getClass(descriptor.getJavaClassName()), mappings);
}
private void addWeight(Class<?> cls, List<DatabaseMapping> mappings) {
Map<String, Integer> fieldOrderMap = getColumnPositions(cls, null);
for (DatabaseMapping mapping : mappings) {
String key = mapping.getAttributeName();
Object obj = fieldOrderMap.get(key);
int weight = 1;
if (obj != null) {
weight = Integer.parseInt(obj.toString());
}
mapping.setWeight(weight);
}
}
private Class<?> getClass(String javaFileName) throws ClassNotFoundException {
Class<?> cls = null;
if (javaFileName != null && !javaFileName.equals("")) {
cls = Class.forName(javaFileName);
}
return cls;
}
private Map<String, Integer> getColumnPositions(Class<?> classFile, Map<String, Integer> columnOrder) {
if (columnOrder == null) {
columnOrder = new HashMap<>();
}
Field[] fields = classFile.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ColumnPosition.class)) {
ColumnPosition cp = field.getAnnotation(ColumnPosition.class);
columnOrder.put(field.getName(), cp.position());
}
}
if (classFile.getSuperclass() != null && classFile.getSuperclass() != Object.class) {
this.getColumnPositions(classFile.getSuperclass(), columnOrder);
}
return columnOrder;
}
}
The DescriptorCustomizer interface containes the method customize() that you should override. If you notice, this class customize() method will get call for every JPA entity class you have in your system. Following is a brief introduction of what each method does in this class.
customize() method
First we informing Eclipse Link that it should use provided weight information by us, using following call.
descriptor.setShouldOrderMappings(true);
Then it getting the mapping information of current processing JPA entity class and calling the addWeight() method. Inside addWeight() method, we calling the getClass() method that is using to get the Class object using the Java class name.
addWeight() method
This is the method that assign the defined weight in JPA entity class to each field. First it will read all annotations in the current JPA entity class by using the getColumnPositions() method.
getColumnPositions() method is a simple method that going through each field in the passed class and find the ColumnPostion annotation and reading the value of it. Note that this is recursive method. The reason to do this is that the current processing class maybe having base classes, that needs to be scan for ColumnPosition annotation as well. This will return a map of field names and its defined position value.
Ok. That's about getColumnPositions() method. Now back to the addWeight() method. So now everything is clear inside this method. It just go through the passed mapping details that received by customize() method and getting each field position through the map it received by calling the getColumnPositions() method.
How to use EntityColumnPositionCustomizer class
Now let's see how to use above implementation in a real JPA entity class. What I going to show you is a simple class that keeps very basic information about a student. Even though this implementation only having one entity class, typically there going to be many entity classes and most of the time every class will have some common fields. So it is better to create a super class and inherit those common properties.
Here I'm assuming that you have at least a basic knowledge of JPA as I'm not going to explain about the JPA here.
Following is our base entity class
import java.util.Calendar;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import lk.j2ee.test.common.ColumnPosition;
import lk.j2ee.test.common.EntityColumnPositionCustomizer;
import org.eclipse.persistence.annotations.Customizer;
@MappedSuperclass
@Customizer(EntityColumnPositionCustomizer.class)
public class BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "id")
@ColumnPosition(position = 0)
private int id;
@Column(name = "created_at", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
@ColumnPosition(position = 30)
private Calendar createdDate;
@Column(name = "updated_at", nullable = false)
@Temporal(TemporalType.TIMESTAMP)
@ColumnPosition(position = 31)
private Calendar updatedDate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Calendar getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Calendar createdDate) {
this.createdDate = createdDate;
}
public Calendar getUpdatedDate() {
return updatedDate;
}
public void setUpdatedDate(Calendar updatedDate) {
this.updatedDate = updatedDate;
}
}
In the above class please note that how we have used the implemented column customizer using the @Customizer(EntityColumnPositionCustomizer.class). Also not that the using of @ColumnPosition(position = 0). Some of you may notice that I have given higher nos for createdDate and updatedDate field. That is because I always wanted them to appear at the end of all the columns in the table. Likewise I wanted id column as the first column in the table.
import javax.persistence.Column;
import javax.persistence.Entity;
import lk.j2ee.test.common.ColumnPosition;
@Entity(name = "student")
public class Student extends BaseEntity {
@Column(name = "name", nullable = false, length = 150)
@ColumnPosition(position = 1)
private String name;
@Column(name = "age", nullable = false)
@ColumnPosition(position = 2)
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Above is the simple Student class. It also contains the @ColumnPosition annotation. Since we already defined the Eclipse Link Customizer at the base class, we do not need to redefine it in here.
So that is is it. If you see any problems or any improvements that should be apply, feel free to post.
5 comments:
Great post. Cannot get it to work with @Id columns though. Whatever weight I assign to such columns they always come first in the table.
Some more info: The method in this blog will not work for columns that are part of a composite PK, but will work for all other columns. I've figured out that the only way to control column order in a composite PK is to use the EclipseLink @PrimaryKey annotation which allows you to specify PK column order.
@phansson You are correct. Thank you very much for posting your findings.
The whole sample works if all ColumnPositions are set. But the problem appears if you haven't set them for all... :-/
To fix this issue you have to set the default _weight_ to at least 99 (or heigher) instead of 1, i.e.
int weight= Integer.MAX_VALUE;
Very useful blog.Thanks to share such valuable post.
mobile service center in velachery
mobile service center in vadapalani
mobile service center in porur
best mobile service center
Post a Comment