I have to following code to check whether the entity in my model has a nullable=false or similar annotation on a field.
import javax.persistence.Column;
import .....
private boolean isRequired(Item item, Object propertyId) {
Class<?> property = getPropertyClass(item, propertyId);
final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = property.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
....
return false;
}
Here's a snippet from my model.
import javax.persistence.*;
import .....
#Entity
#Table(name="m_contact_details")
public class MContactDetail extends AbstractMasterEntity implements Serializable {
#Column(length=60, nullable=false)
private String address1;
For those people unfamiliar with the #Column annotation, here's the header:
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface Column {
I'd expect the isRequired to return true every now and again, but instead it never does.
I've already done a mvn clean and mvn install on my project, but that does not help.
Q1: What am I doing wrong?
Q2: is there a cleaner way to code isRequired (perhaps making better use of generics)?
property represents a class (it's a Class<?>)
#Column and #JoinColumn can only annotate fields/methods.
Consequently you will never find these annotations on property.
A slightly modified version of your code that prints out whether the email property of the Employee entity is required:
public static void main(String[] args) throws NoSuchFieldException {
System.out.println(isRequired(Employee.class, "email"));
}
private static boolean isRequired(Class<?> entity, String propertyName) throws NoSuchFieldException {
Field property = entity.getDeclaredField(propertyName);
final JoinColumn joinAnnotation = property.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = property.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
return false;
}
Note that this is a half-baked solution, because JPA annotations can either be on a field or on a method. Also be aware of the difference between the reflection methods like getFiled()/getDeclaredField(). The former returns inherited fields too, while the latter returns only fields of the specific class ignoring what's inherited from its parents.
The following code works:
#SuppressWarnings("rawtypes")
private boolean isRequired(BeanItem item, Object propertyId) throws SecurityException {
String fieldname = propertyId.toString();
try {
java.lang.reflect.Field field = item.getBean().getClass().getDeclaredField(fieldname);
final JoinColumn joinAnnotation = field.getAnnotation(JoinColumn.class);
if (null != joinAnnotation) {
return !joinAnnotation.nullable();
}
final Column columnAnnotation = field.getAnnotation(Column.class);
if (null != columnAnnotation) {
return !columnAnnotation.nullable();
}
} catch (NoSuchFieldException e) {
//not a problem no need to log this event.
return false;
}
}
Related
I am currently working on an application connected to a MongoDB instance. I am having trouble where the 'id' field of my object is not being returned to me within the application but is being returned as null.
The schema has an 'entity' as defined below:
{
"entity_id": String,
"parent": String,
"relevance": boolean
}
I'm querying the collection using the Java Sync Driver (4.4.1) like so:
try {
Entity testDoc = collection.find(eq("entity_id", entity_id)).first();
if (testDoc != null) {
//add entity to a list
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Failed to get Entity", e);
}
For some reason this will give me every field in the object when I query EXCEPT the entity_id. I keep getting this returned as:
entity_id= null
Two things stick out to me. The first being that every other field is a String (originally the Id was a UUID object but I simplified while troubleshooting) and they still return if it's other fields. The second being that there is a whitespace before this null value as if it's being formatted. Other null values return as field=null instead of field= null
I was looking to see if there is some security setting preventing things from being labeled as *_id or *id from being returned but I have found no such instance.
Edit: Here is the Entity Pojo for clarity
public class Entity {
#BsonProperty(value = "entity_id")
private String entityID;
#BsonProperty(value = "parent")
private String parent;
#Deprecated
#BsonProperty(value = "relevance")
private boolean relevance;
public Entity() {}
public Entity(String entityID, String parent, Boolean relevance) {
this.entityID = entityID;
this.parent = parent;
this.relevance = relevance;
}
public String getEntityID() {
return entityID;
}
public void setEntityID(String entityID) {
this.entityID = entityID;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
public boolean isRelevant() {
return relevance;
}
public void relevance(boolean relevance) {
this.relevance = relevance;
}
}
So update for anyone watching this, it appears to have been an issue with my Eclipse IDE.
I reimported the project into IntelliJ Community Edition and rebuilt the Maven project, etc... After doing so, the test cases passed and my entityID returns in the query. Hopefully if anyone else runs into this issue they can do something similar.
Is there any clean way to check if they are all null or not for example getDescription(), getName(), getScript(), getTargets() and getTrigger() is null or not, checking in one line?
ruleBean.setDescription(rule.getDescription());
} else if (rule.getName() != null) {
ruleBean.setName(rule.getName());
} else if (rule.getScript() != null) {
ruleBean.setScript(rule.getScript());
} else if (rule.getTargets() != null) {
ruleBean.setTargets(rule.getTargets());
} else if (rule.getTrigger() != null) {
ruleBean.setTrigger(rule.getTrigger());
} else {
return ResponseBean.builder().withData(request.getData())
.withMessage("No data provided for rule update").asFailure().build();
} ```
You can write a single condition with Optional:
if (rule.getName() != null) {
ruleBean.setName(rule.getName());
}
becomes:
Optional.ofNullable(rule.getName()).ifPresent(ruleBean::setName);
It's harder to chain this to give the "if else" behaviour you have, though.
It looks like what you're trying to detect with the "if/else" is whether some update was performed. To achieve this, you could have a method like:
<T> boolean did(T value, Consumer<? super T> consumer) {
if (value == null) return false;
consumer.accept(value);
return true;
}
Then you can write your chain as:
boolean didSomething =
did(rule.getName(), ruleBean::setName)
|| did(rule.getScript(), ruleBean::setScript) /* etc */;
if (!didSomething) {
// Return your error response.
}
Because of the short-circuiting behaviour of ||, this will stop after the first call to did which "did" something, like the if/else if.
And if you actually want to apply the update for any non-null value, simply change || to |. The value of didSomething is still false if none of the conditions matched, as before.
Java validation API (JSR-380) can be very handy in this type of problems, if you can afford the dependencies.
In this specific case, you can just annotate your bean:
import javax.validation.constraints.NotNull;
// ...
public class Rule {
#NotNull
private String description;
#NotNull
private String name;
#NotNull
private String script;
#NotNull
private String targets;
#NotNull
private String trigger;
}
(there are more built-in constraint definitions)
And then validate using Hibernate Validator (the reference implementation of the validation API):
Rule rule = new Rule();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Rule>> violations = validator.validate(rule);
if (violations.isEmpty()) {
//all good!
} else {
//bean is not valid
}
In order to use this approach, you need two new dependencies in your pom.xml:
Java Validation API:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
and Hibernate Validator
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
If you have a lot of attributes in rule need to check, you can use reflection to make it clean a little.
If you just have a few attributes need to check, the if solution is a clean and readable way to do it.
Waring: the reflection solution may lead to a performance issue.
example code:
test.java
public class test{
public static void main(String[] args)
{
Foo rule = new Foo();
Foo ruleBean = new Foo();
rule.setName("foo");
rule.setTargets(1);
# the field you need to check, not necessary to check all attributes.
List<String> fields = Arrays.asList("Name", "Script", "Targets");
for (String field : fields) {
try {
Object temp = rule.getClass().getMethod("get"+field).invoke(rule);
if (temp != null) {
ruleBean.getClass().getMethod("set"+field,temp.getClass()).invoke(ruleBean,temp);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
System.out.println(ruleBean.getName());
System.out.println(ruleBean.getScript());
System.out.println(ruleBean.getTargets());
}
}
Foo.java
public class Foo {
private String name;
private String script;
private Integer targets;
public void setName(String s) {
this.name = s;
}
public String getName() {
return this.name;
}
public void setScript(String s) {
this.script = s;
}
public String getScript() {
return this.script;
}
public void setTargets(Integer s) {
this.targets = s;
}
public Integer getTargets() {
return this.targets;
}
}
result:
foo
null
1
This question seems awkward but we are facing a strange behaviour while retrieving the PropertyDescriptors of a javabean.
Here are the execution results on 1.6, 1.7 and 1.8 of a simple piece of code, compiled with 1.6 compliance.
Java 1.6 execution:
java.beans.PropertyDescriptor#4ddc1428 <- Not important
java.beans.IndexedPropertyDescriptor#7174807e <- Yes I have an indexed property
Java 1.7 execution:
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important
java.beans.IndexedPropertyDescriptor[name=values; indexedPropertyType=class java.lang.String; indexedReadMethod=public java.lang.String JavaBean.getValues(int)] <- Yes I have an indexed property
Java 1.8 execution:
java.beans.PropertyDescriptor[name=class; propertyType=class java.lang.Class; readMethod=public final native java.lang.Class java.lang.Object.getClass()] <- Not important
java.beans.PropertyDescriptor[name=values; propertyType=interface java.util.List; readMethod=public java.util.List JavaBean.getValues()] <- Ouch! This is not an indexed property anymore!
Why has it changed?
The javabean specs states about accessing a property with an index. It is not said as mandatory to use an array as the container of the indexed property. Am I wrong?
I read the specs and chapter 8.3.3 talks about Design Patterns for Indexed properties, not the strict rule.
How to make the previous behaviour coming back again without refactoring all the app ? < Old application, lot of code to modify, etc...
Thanks for the answers,
JavaBean class
import java.util.ArrayList;
import java.util.List;
public class JavaBean {
private List<String> values = new ArrayList<String>();
public String getValues(int index) {
return this.values.get(index);
}
public List<String> getValues() {
return this.values;
}
}
Main class
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class Test {
public static void main(String[] args) throws IntrospectionException {
PropertyDescriptor[] descs =
Introspector.getBeanInfo(JavaBean.class).getPropertyDescriptors();
for (PropertyDescriptor pd : descs) {
System.out.println(pd);
}
}
}
From JavaBeans 1.01 specification, section 7.2 “Indexed properties”:
A component may also expose an indexed property as a single array value.
Section 8.3 is describing the design patterns which introspection recognizes, in the absence of explicit BeanInfo. Section 8.3.3 is saying that only array properties will trigger automatic recognition of indexed properties.
You're technically correct; it is not mandatory to use an array. But if you don't, the spec says you have to provide your own BeanInfo to expose the property as an indexed property.
So the answer to your question's title is: Yes, Java 1.8 is JavaBean specs compliant.
I'm not sure why List properties were ever supported. Maybe a future JavaBeans specification was going to support them which has since been withdrawn.
As to your final question: I think you'll have to create a BeanInfo class for each class with List properties. I expect you can create a general superclass to make it easier, something like:
public abstract class ListRecognizingBeanInfo
extends SimpleBeanInfo {
private final BeanDescriptor beanDesc;
private final PropertyDescriptor[] propDesc;
protected ListRecognizingBeanInfo(Class<?> beanClass)
throws IntrospectionException {
beanDesc = new BeanDescriptor(beanClass);
List<PropertyDescriptor> desc = new ArrayList<>();
for (Method method : beanClass.getMethods()) {
int modifiers = method.getModifiers();
Class<?> type = method.getReturnType();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers) &&
!type.equals(Void.TYPE) &&
method.getParameterCount() == 0) {
String name = method.getName();
String remainder;
if (name.startsWith("get")) {
remainder = name.substring(3);
} else if (name.startsWith("is") &&
type.equals(Boolean.TYPE)) {
remainder = name.substring(2);
} else {
continue;
}
if (remainder.isEmpty()) {
continue;
}
String propName = Introspector.decapitalize(remainder);
Method writeMethod = null;
Method possibleWriteMethod =
findMethod(beanClass, "set" + remainder, type);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(Void.TYPE)) {
writeMethod = possibleWriteMethod;
}
Class<?> componentType = null;
if (type.isArray()) {
componentType = type.getComponentType();
} else {
Type genType = method.getGenericReturnType();
if (genType instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) genType;
if (p.getRawType().equals(List.class)) {
Type[] argTypes = p.getActualTypeArguments();
if (argTypes[0] instanceof Class) {
componentType = (Class<?>) argTypes[0];
}
}
}
}
Method indexedReadMethod = null;
Method indexedWriteMethod = null;
if (componentType != null) {
Method possibleReadMethod =
findMethod(beanClass, name, Integer.TYPE);
Class<?> idxType = possibleReadMethod.getReturnType();
if (idxType.equals(componentType)) {
indexedReadMethod = possibleReadMethod;
}
if (writeMethod != null) {
possibleWriteMethod =
findMethod(beanClass, writeMethod.getName(),
Integer.TYPE, componentType);
if (possibleWriteMethod != null &&
possibleWriteMethod.getReturnType().equals(
Void.TYPE)) {
indexedWriteMethod = possibleWriteMethod;
}
}
}
if (indexedReadMethod != null) {
desc.add(new IndexedPropertyDescriptor(propName,
method, writeMethod,
indexedReadMethod, indexedWriteMethod));
} else {
desc.add(new PropertyDescriptor(propName,
method, writeMethod));
}
}
}
propDesc = desc.toArray(new PropertyDescriptor[0]);
}
private static Method findMethod(Class<?> cls,
String name,
Class<?>... paramTypes) {
try {
Method method = cls.getMethod(name, paramTypes);
int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) &&
!Modifier.isStatic(modifiers)) {
return method;
}
} catch (NoSuchMethodException e) {
}
return null;
}
#Override
public BeanDescriptor getBeanDescriptor() {
return beanDesc;
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return propDesc;
}
}
I am facing the same issue. I am trying to save StartDate and End date as List from JSP but it is not saved and values are wiped out. In my project, there are start date and end date fields. I debugged BeanUtilsBean then I observed that fields do not have writeMethod. I have added one more setter method for each field in my class and it works.
java.beans.PropertyDescriptor[name=startDateStrings; propertyType=interface java.util.List; readMethod=public java.util.List com.webapp.tradingpartners.TradingPartnerNewForm.getStartDateStrings()]
This is a JavaBeans crapspec problem, which only allows void setters. If you want this-returning setters, then you can't have JavaBeans compatibility and there's nothing Lombok could do about it.
In theory, you could generate two setters, but then you'd have to call them differently and having two setters per field is simply too bad.
<cc:dateInput property='<%= "startDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
<cc:dateInput property='<%= "endDateStrings[" + row + "]" %>' onchange="setPropertyChangedFlag()"/>
public List<String> getStartDateStrings() {
return startDateStrings;
}
public String getStartDateStrings(int index) {
return startDateStrings.get(index);
}
public void setStartDateStrings(int index, String value) {
startDateStrings.set(index, value);
}
public List<String> getEndDateStrings() {
return endDateStrings;
}
public String getEndDateStrings(int index) {
return endDateStrings.get(index);
}
public void setEndDateStrings(int index, String value) {
endDateStrings.set(index, value);
I have an entity which has some BIT fields into the database:
editable
needs_review
active
These fields are mapped against boolean fields in its Java class using Hibernate 3.6.9 version. That forces me to write an interface method for each List of entities I want to get:
List<Entity> listEditables();
List<Entity> listReviewNeeded();
List<Entity> listActives();
Or write a general interface method to achieve a combination of them:
List<Entity> listEntities(boolean editables, boolean reviewNeeded, boolean actives);
That second choice looks greater, but if I add another field in the future there will be a need to modify the interface itself (and every line of code coupled to it).
So I decided I can express it as an enumeration Set:
public enum EntityType{
EDITABLE, REVIEW_NEEDED, ACTIVE
}
//That way there's no need to change interface method's signature
List<Entity> listEntities(Set<EntityType> requiredTypes);
It makes sense that being an enumeration match what I want to achieve, the Entity type itself should have its own Set<EntityType>:
public class Entity{
Set<EntityType> entityTypes;
}
However instead of that I have the mapped booleans which logically match that Set. Then my question, is there any way to map Set<EntityType> entityTypes in hibernate based in that BIT fields or do I have to manage that logic myself having them as boolean?
UPDATE
Having them mapped as a Set implies the possibility of querying for a List using an in clause, if not it would imply an extra step for conversion between my controller and model codes.
Set<EntityType> typesSet = Sets.newHashSet(EntityType.EDITABLE, EntityType.REVIEW_NEEDED);
//Obtains a list of every single entity which is EDITABLE or REVIEW_NEEDED
session.createCriteria(Entity.class).addRestriction(Restrictions.in("entityTypes",typeSet)).list();
I think I have a solution for you. What you are interested in is a CompositeUserType.
As an example lets use a InetAddress composite user type I wrote lately to map a 128bit IPv6 Address / IPv4Address object to two 64bit long properties inside a user account entity.
The signupIp:InetAddress is mapped towards two columns (there is no column count limit or alike) using:
#Columns(columns = {#Column(name = "ip_low", nullable = true), #Column(name = "ip_high", nullable = true)})
private InetAddress signupIp;
And the interesting part of the implementation looks like this:
public class InetAddressUserType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String [] {"ipLow", "ipHigh"};
}
#Override
public Type[] getPropertyTypes() {
return new Type [] { LongType.INSTANCE, LongType.INSTANCE};
}
#Override
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null)
return toLong((InetAddress)component)[property];
else
return null;
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
long [] longs = toLong((InetAddress)value);
st.setLong(index, longs[0]);
st.setLong(index + 1, longs[1]);
}
else {
st.setNull(index, LongType.INSTANCE.sqlType());
st.setNull(index + 1, LongType.INSTANCE.sqlType());
}
}
#Override
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException {
throw new RuntimeException("This object is immutable");
}
#Override
public Class<?> returnedClass() {
return InetAddress.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x != null ? x.equals(y) : null == y;
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Long ipLow = rs.getLong(names[0]);
if(!rs.wasNull()) {
Long ipHigh = rs.getLong(names[1]);
try {
return fromLong(new long [] {ipLow, ipHigh});
} catch (UnknownHostException e) {
throw new HibernateException("Failed to get InetAddress: ip = " + ipHigh + " + " + ipLow, e);
}
}
else
return null;
}
#Override
public Object deepCopy(Object value) throws HibernateException {
if(value != null)
try {
return InetAddress.getByAddress(((InetAddress)value).getAddress());
} catch (UnknownHostException e) {
throw new RuntimeException("Impossible Exception: " + e.getMessage(), e);
}
else
return null;
}
#Override
public boolean isMutable() {
return false;
}
...
}
Note that I flexibly switch between Inet4Address and Inet6Address instances depending on the values of ipLow and ipHigh. The composite is marked as immutable and you need to check the documentation and the examples in the Hibernate source code (build in composite user types).
In a similar way you can map your meaningful bit properties. You can query those bits by using a single Restriction.eq refering to your EnumType. You can use the equals method to check the properties object. And if you need to refer to a special mapped bit you can use the dot notation like in signupIp.ipLow to refer to the ipLow property/column.
I guess this is what you are looking for.
Update:
In the end it boils down to define the right order of your properties. Hibernate will always use integer index values to access each property:
//immutable for simplicity
class Status {
private final boolean editable;
private final boolean needsReview;
private final boolean active;
//... constructor + isEditable etc..
}
In your StatusCompositeType class:
public String[] getPropertyNames() {
return new String [] {"editable", "needsReview", "active"};
}
public Type[] getPropertyTypes() {
return new Type [] { BooleanType.INSTANCE, LongType.INSTANCE};
}
public Object getPropertyValue(Object component, int property) throws HibernateException {
if(component != null) {
Status status = (Status)component;
switch(property) {
case 1: return status.isEditable();
case 2: return status.isReviewNeeded();
case 3: return status.isActive();
default: throw new IllegalArgumentException();
}
}
else
return null; //all columns can be set to null if you allow a entity to have a null status.
}
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value != null) {
Status status = (Status)value;
st.setBoolean(index, status.isEditable());
st.setBoolean(index + 1, status.isReviewNeeded());
st.setBoolean(index + 2, status.isActive());
}
else {
st.setNull(index, BooleanType.INSTANCE.sqlType());
st.setNull(index + 1, BooleanType.INSTANCE.sqlType());
st.setNull(index + 2, BooleanType.INSTANCE.sqlType());
}
}
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Boolean isEditable = rs.getBoolean(names[0]);
if(!rs.wasNull()) {
Boolean isReviewNeeded = rs.getBoolean(names[1]);
Boolean isActive = rs.getBoolean(names[2]);
return new Status(isEditable, isReviewNeeded, isActive);
}
else
return null;
}
The rest is straight forward. Remember to implement equals and hashcode for the user type and add the type to the configuration before you create your sessionFactory.
Once you have everything in place you can create a criteria search and use:
//search for any elements that have a status of editable, no reviewNeeded and is not active (true false false).
criteria.add(Restrictions.eq("status", new Status(true, false, false));
Now your listEntities method may become either: listEntities(Status status) or listEntities(boolean editable, boolean reviewNeeded, boolean isActive).
If you need additional information just check the CompositeType and BasicType implementations Hibernate provides within its own sourcecode (look for implementors of CompositeType and BasicType). Understanding those helps alot to use and learn this intermediate level knowledge of Hibernate.
After some brainstorming, I've gone to a workaround which I consider the second best one being imposible to map an enum for the booleans in Hibernate. This is how I have my Entity class looks now:
public class Entity{
private boolean editable;
private boolean needsReview;
private boolean active;
//getters and setters
}
My listing method is implemented as this:
public List<Entity> listEntities(Set<EntityType> requiredTypes){
Criteria cri = session.createCriteria(Entity.class);
if (requiredTypes.contains(EntityType.EDITABLE)){
cri.addRestriction(Restrictions.eq("editable",true));
}
if (requiredTypes.contains(EntityType.NEEDS_REVIEW)){
cri.addRestriction(Restrictions.eq("needsReview",true));
}
if (requiredTypes.contains(EntityType.ACTIVE)){
cri.addRestriction(Restrictions.eq("active",true));
}
return cri.list();
}
Not bad, but don't know if it's the only way to go with that!
I don't think hibernate provides a way to manage the mappings the way you're describing. You can create your own UserType (https://community.jboss.org/wiki/Java5EnumUserType) but every time you add a new enum value you will have to change the logic in the UserType to map the new field as well.
The alternative will be to convert this into a one to many relationship. Your point is basically that if you want to add more fields you will have to change the signature of listEntities but also you will have to modify your table.
So, instead you can create a table that will contain your entity types and have a #OneToMany` relationship to it from your entity. For example:
Define your flags as required:
public enum Flags {
EDITABLE, REVIEW_NEEDED, ACTIVE
}
Create a one-to-many relationship to EntityType:
#Entity
#Table( name="entity" )
public class Entity implements Serializable {
#OneToMany(mappedBy = "entity")
public Set<EntityType> getEntityTypes() {
return entityTypes;
}
And a many-to-one to Entity:
#Entity
#Table( name="entityType" )
public class EntityType implements Serializable {
#Id
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ENTITY_ID")
private Entity entity;
#Enumerated(EnumType.STRING)
private Flag entityType;
...
}
PD: Please note the code is just an example and is not complete or tested.
I have a db table with column of datatype char(20). I'm not allowed to change it to a varchar.
I'm writing a JPA entity mapped to this table. I would like the string field representing this column in my entity class to always contain the trimmed value, not the 20-character value padded with spaces that exists in the db.
I can't see any easy way to do this. (an annotation would rock!). At the moment I'm just returning a trimmed value from my getter(), but this feels like a kludge.
A google search is offering no help on this. Any ideas?
Or you can use lifecycle annotations:
#Entity
public class MyEntity {
#PostLoad
protected void repair(){
if(myStringProperty!=null)myStringProperty=myStringProperty.trim();
}
private String myStringProperty;
public String getMyStringProperty() {
return myStringProperty;
}
public void setMyStringProperty(String myStringProperty) {
this.myStringProperty = myStringProperty;
}
}
If this occurs on multiple entities you can create a custom annotation and write a dedicated EntityListener.
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Trim {}
Listener
public class TrimListener {
private final Map<Class<?>, Set<Field>> trimProperties =
new HashMap<Class<?>, Set<Field>>();
#PostLoad
public void repairAfterLoad(final Object entity) throws Exception {
for (final Field fieldToTrim : getTrimProperties(entity.getClass())) {
final String propertyValue = (String) fieldToTrim.get(entity);
if (propertyValue != null)
fieldToTrim.set(entity, propertyValue.trim());
}
}
private Set<Field> getTrimProperties(Class<?> entityClass) throws Exception {
if (Object.class.equals(entityClass))
return Collections.emptySet();
Set<Field> propertiesToTrim = trimProperties.get(entityClass);
if (propertiesToTrim == null) {
propertiesToTrim = new HashSet<Field>();
for (final Field field : entityClass.getDeclaredFields()) {
if (field.getType().equals(String.class)
&& field.getAnnotation(Trim.class) != null) {
field.setAccessible(true);
propertiesToTrim.add(field);
}
}
trimProperties.put(entityClass, propertiesToTrim);
}
return propertiesToTrim;
}
}
Now annotate all relevant String fields with #Trim and register the Listener as default entity listener in your persistence.xml:
<persistence-unit ..>
<!-- ... -->
<default-entity-listeners>
com.somepackage.TrimListener
and.maybe.SomeOtherListener
</default-entity-listeners>
</persistence-unit>
The accepted answer (using JPA entity listeners / #Trim annotation) is a dangerous one. Calling the setter on the retrieved entity appears to mark the entity as dirty. When I tried this myself at a root entity level (using Spring3 / hibernate), it triggered tons of extraneous updates to related entities that were otherwise not modified during the transaction. It was a real mess in production, and tracking it down to this being the cause took time.
In the end I opted to go with the more straightforward approach of trimming each of the fields manually on-demand (in a custom entity-to-domain mapper, or in the entity getter) similar to Edwin's answer.
It's an old question but it was very useful for me to get to my answer. In my case, the easiest way was to create a simple "javax.persistence.Converter", like this:
#Converter
public class StringTrimConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String attribute) {
return attribute;
}
#Override
public String convertToEntityAttribute(String dbData) {
return dbData.trim();
}
}
And you can use it like this:
#Entity
#Table(name = "ViewAddress")
public class PostalAddress extends DbObject {
#Convert(converter = StringTrimConverter.class)
private String street;
#Convert(converter = StringTrimConverter.class)
private String number;
(...)
It works just fine.
Put the annotation on the getter method, set the #Acesss to AccessType.Property and trim the field there using String.trim() method.
Or simply put the trim in the getter method and always access the field through it. It is not going to get any simpler than that.
If you do not mind using pure Hibernate and deviating from the JPA standard you can use Hibernate #ColumnTransformer provided that you have a database function to do the work of
You can find how to do it in the Hibernate reference:
http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/mapping.html#mapping-column-read-and-write
I hope that helps!
I am using this method, which makes the trimming transparent without having to use annotations in every string field.
In the same package that you have your session factory class (the one you use to get Sessions, e.g org.blablabla.yourpackage.etc.SessionGetter.getSession(), you must create a file named package-info.java and put this content inside it:
#TypeDefs({
#TypeDef(name = "trimmedStringType",
defaultForType = String.class,
typeClass = StringUserType.class)
})
package org.blablabla.yourpackage.etc;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
Then you create the class StringUserType in this same package:
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.usertype.EnhancedUserType;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
public class StringUserType implements EnhancedUserType, Serializable {
private static final int[] SQL_TYPES = new int[]{Types.VARCHAR};
#Override
public int[] sqlTypes() {
return SQL_TYPES;
}
#Override
public Class returnedClass() {
return String.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
}
if (x == null || y == null) {
return false;
}
String dtx = (String) x;
String dty = (String) y;
return dtx.equals(dty);
}
#Override
public int hashCode(Object object) throws HibernateException {
return object.hashCode();
}
#Override
public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Object s = StandardBasicTypes.STRING.nullSafeGet(resultSet, names, session, owner);
if (s == null) {
return null;
}
return s.toString().trim();
}
#Override
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if (value == null) {
StandardBasicTypes.STRING.nullSafeSet(preparedStatement, null, index, session);
} else {
StandardBasicTypes.STRING.nullSafeSet(preparedStatement, value.toString().trim(), index, session);
}
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
#Override
public Object assemble(Serializable cached, Object value) throws HibernateException {
return cached;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
#Override
public String objectToSQLString(Object object) {
throw new UnsupportedOperationException();
}
#Override
public String toXMLString(Object object) {
return object.toString();
}
#Override
public Object fromXMLString(String string) {
return string;
}
}
And that's it, no need to create custom annotations in your beans, it will "magically" trim the strings whenever you get the objects from the database.
What JPA provider are you using?
If you are using EclipseLink CHAR fields are trimmed by default. You can disable this through the session trimStrings property (ensure you have not set this).
Accepted answer works except registering the listener in persistence.xml. I did it with orm.xml.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
version="2.1">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.example.TrimListener">
</entity-listener>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
All you have to do is put this on your controller and it works as expected you dont need a listener or anything of that
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
If your domain requirement states it needs trimmed information, you need to store the data in trimmed value. I see nothing wrong about it.
Domain Model
An object model of the
domain that incorporates both behavior
and data. (PEAA - Martin Fowler)
If you explicitly have to enforce the business rule at the database level, one option is that you have a choice of writing a trigger, you can use built-in SQL trim method. But it will be like using a rocket to crack an egg.