Using Hibernate annotations to persist custom value for Enumerated attribute - java

I have lots of Java enums that I persist with Hibernate. As far as I know there are two different standard ways to persist these enums:
#Enumerated(EnumType.ORDINAL)
This is the default, and it just persists the ordinal value from the enum.
#Enumerated(EnumType.STRING)
This persists the name of the enum value.
Those have worked fine for me so far, but now I have a new enum where I want to persist a custom value from the enum that is neither the ordinal nor the name. Is this possible? I searched around and saw lots of people asking how to persist the name, which is easily accomplished using EnumType.STRING, but I want to persist an int that can be used for comparison in my SQL queries. I tried overriding toString() to return my custom value, but that did not work.
I'll paste my java enum below. I want to persist the int value member from the enum.
Thanks in advance!
public enum Permission {
VIEW (4),
CHANGE(6),
FULL(7);
private int value;
Permission(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

You can implement a UserType with desired behaviour and configure Hibernate to use it with #Type annotation.
See, for example, UserType for persisting a Typesafe Enumeration with a VARCHAR column
.

Yes, I'm missing a JPA solution, too.
Workaround:
private int permissionValue;
private transient Permission permission;
Then convert in the getter/setter of permission.
Maybe you also need to implement some lifecycle methods? (I'm not sure.)
http://download.oracle.com/docs/cd/B32110_01/web.1013/b28221/undejbs003.htm#BABIAAGE

Related

variable id might not have been initialized Spring Boot Controller with lombok

I am trying to add a simple controller method, but I am running into the following
Exercise.java:[13,1] variable id might not have been initialized
Here is the code that I am working with
#RequestMapping(value = "/exercises/{id}")
public ResponseEntity<Optional<Exercise>> getExerciseById(Model model, #PathVariable Integer id) {
Optional<Exercise> exercise = exerciseRepository.findById(id);
if(id!=null)
return new ResponseEntity<Optional<Exercise>>(exercise, HttpStatus.OK);
else
return new ResponseEntity<Optional<Exercise>>(exercise, HttpStatus.NOT_FOUND);
}
I am using an Optional<Exercise> here because I am taking advantage of the build in method findById from the JpaRepository package. I haven't found any good posts on how to handle this is. This is probably something simple. I've found some documentation around this:https://www.java67.com/2016/07/how-to-fix-variable-might-not-have-been-initialized-error-in-java.html, but I could use a little help understanding the best way to fix this. This is the exercise classe
#Entity
#Table(name = "exercise")
#Value
#NoArgsConstructor
public class Exercise {
#Id
#NonNull
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private int chapterId;
private String exercise;
private String answer;
private String question;
private String a;
private String b;
private String c;
}
tldr;
I don't think JPA plays well with the immutable entity created by #Value. Use #Data instead.
ihnbtdttrt (i have nothing better to do than to read this);
This partially guesswork, but since it has seemed to help, this is what I think is happening:
When you call findById(), JPA creates a new entity object using a no-argument constructor, and then sets the fields individually afterwards. (I'm not sure if it uses setters or sets the fields directly using reflection).
The #Value annotation, as documented here, makes a class immutable, with all the fields private and final and with no "setters" created. This means that the only way to set the fields is by passing the field values into a constructor with appropriate arguments. After that the fields are not changeable.
Since JPA initializes entities using the no-args constructor and tries to set the fields afterwards, with your setup, it uses the no-args constructor and ends up with an entity object where none of the fields have been initialized but none of them are modifiable after the constructor. All private, final fields, with no setters. Then it tries to call entity.getId(), and the id field hasn't been initialized, leading to the above error.
To fix this, you can use the #Data annotation instead of #Value. This is similar, but doesn't create an immutable object. In particular, it generates "setter" functions and the fields are not set to final. This is the type of Java bean that JPA expects, one that can be initialized with a no-argument constructor and then have the fields set afterwards.
There may be ways to configure JPA to create objects differently, so that it passes all the data into a constructor so that you can have immutable entities. I know that some Spring DI stuff is configurable to initialize using rich constructors like this, but Idk about JPA.
For what it's worth, I appreciate the value of immutable objects for clean code, but it's not uncommon to find the above pattern of no-arg construction + post-construction setting when using the popular Java frameworks like JPA/Hibernate, Spring, etc. They don't always play well with immutability.

How to Persist a specific attribute of an Enum in Hibernate?

I have an enum attribute inside a backing bean which haves 2 attributes: "id" (int attribute) and "label" (String attribute). In the DB I need to persist only the "id", but I still need the label value to use on my view. The problem is: #Enumerated only gives me the option to persist using EnumType.ORDINAL or EnumType.STRING. There's any way to persist just the enum's "id" attribute in the DB? (btw... i'm using Hibernate).
Thanks!
fd's comment is in place. Nevertheless, if you're in a real need of this you can tweak the solution a bit, instead of persisting an enum, you can mark it as transient and add a property e.g. enumId to an entity class, that you'll persist instead. Than the only thing left is to synchronize the enum's value through getter and setter method of an enumId
The id and the attribute are two facets of the same thing. They have the same meaning. Storing them both in the DB is redundant, and violates the Third Normal Form of database design.
For a JPA (Hibernate) entity, I suggest you define the ORDINAL (which is the id) as your field and as suggested above by "Master Slave" define the String as a transient. Then define #PostLoad and the setter of the id to convert the ordinal to the name (which is your label) using name() and convert back using valueOf(String name) in the setter of the label.

Injecting fields into Java JPA fields

I'm no pro with Java, so I need a little help. I'm using the Play Framework.
I have an Entity class which extends GenericModel with fields like the following:
#Column(name = "description")
private String description;
I want to add an additional field using a getter, let's call it getToString, which basically contains a read only string with the string representation of the entity.
I need this because the object is getting sent as a JSON response, and my JavaScript will read this field, and display it where for example the entity needs to be represented as a string.
How do I go about doing this?
I'm no expert on the Play framework, but probably you should have a look at the #Transient annotation.
Fields (and getters/setters if you are using JPA property access) marked with #Transient will be ignored by JPA, but usually be considered by other frameworks.
The problem I'm having was a side effect of using GsonBuilder. The builder doesn't appear to be parsing getters and setters, unless the source of the library is modified, which I'm not willing to do.
For what I understand (please correct me if I'm wrong) you want a read-only method that will return a string representation (JSon format) of the entity.
You could just override the default toString method:
#Override
public String toString() {
return "your_json_string";
}
and call it when needed

Read Hibernate entity identity value

Does Hibernate have an API for reading the value of an entity's identity field? In my case, this would be the Serializable value returned by the field annotated with #Id. For example, suppose I had a Person entity:
class Person {
#Id private long id;
// ... other fields, getters/setters, etc ...
}
Person p = new Person();
p.setId(42L);
Hibernate.unknownFunction(p); // returns 42L
Sure I could read the annotations to find the #Id field myself, but this seems like something that might be built in.
session.getIdentifier(object)
Return the identifier value of the given entity as associated with this session. An exception is thrown if the given entity instance is transient or detached in relation to this session.
The object needs to have an ID, and to be associated with the current session, otherwise an exception is thrown. But that is logical, I think.
Well, if you need a method that return id in arbitrary classes, design a interface to satisfy this. for example:
public interface IdHolder {
Integer getId();
}
With such interface, you could make some utility methods to retrive id from arbitrary classes.
The cglib is a robust but a bit of tricky way to do it.
I can't say 100% no - but I really doubt it since not all Entities are annotated with #Id; there are other variants that can be used such as #EmbeddedId. Given this, can't you just use reflection to get at your id value?

Using enum as id

Using JPA, can we define an enum as id of an entity?
I've tried the following:
public enum AssetType {
....
}
#Entity
#IdClass(AssetType.class)
public class Adkeys {
private AssetType type;
#Id
#Enumerated(EnumType.STRING)
#Column(nullable = false)
public AssetType getType() {
return type;
}
}
Using OpenJPA, it complains:
org.apache.openjpa.persistence.ArgumentException: The id class "class aa.AssetType" specified by type "class aa.Adkeys" does not have a public no-args constructor.
So my questions are:
should we able to use enum as id for an entity on JPA? (i.e. there is a bug in OpenJPA)
or do I make a mistake somewhere?
and is there any workaround for such problem?
The JPA spec doesn't say this is possible:
2.1.4 Primary Keys and Entity Identity
The primary key (or field or property of a composite primary key) should be one of the following types: any Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date; java.sql.Date. In general, however, approximate numeric types (e.g., floating point types) should never be used in primary keys. Entities whose primary keys use types other than these will not be portable.
If you really want to have a compile-time fixed number of records for a given entity, you can use a String or int primary key and assign it AssetType.FOO.name() or AssetType.FOO.ordinal()
And non-portable here means that some persistence provider may support other things, but it might not work for another provider. As with the enum - if the persistence provider has special support for it, that does not try to instantiate it, but rather processes it specially after checking if class.isEnum(), then it might work. But it seems your persistence provider doesn't do this.
No, you can't use enums as ID because JPA doesn't allow to define your own mapping for ID columns (they must be int or long or something that JPA can create with new).
IDs must not be the business key (in your case: the type). Using the business key as an ID is a common mistake in DB designs and should be avoided because it will cause all kinds of problems later.
Add an independent ID column to solve the problem.
OpenJPA is the only JPA provider that does not support this.
See Support Enum as Primary Key Type
Do you really want to do this? This construct doesn't allow changing the database enum keys without updating the enum in the code (fail on load), nor the other way around (constraint failure). Why don't you just create an AssetType table with int pk and name, and make the Adkeys have a foreign key to AssetType.id as pk?
You can load the AssetTypes from the db on startup if you need to enumerate them in your app.

Categories

Resources