Hibernate only sets length within #Column on field, not attribute - java

I have some classes with which I'm exploring Hibernate. One of them has a name field, and I attempted to set the length of it with the following:
private String firstname;
#Column(length=25)
public String getFirstName() { return firstName; }
public void setFirstName(String first) { this.firstName = first; }
I did this with several fields, all in the same pattern -- I put the #Column on the property firstName instead of on the field firstName. I have read that this determines how the framework accesses your field's information -- either directly from the field, or from the getter/setter of the field.
The idea that length can be put in #Column in that position is confirmed somewhat in the documentation; in the Hibernate Reference Documentation, in section 5.1.4.1.4., "Declaring column attributes", it has the following lines:
#Column(updatable = false, name = "flight_name", nullable = false, length=50)
public String getName() { ... }
so they put the length attribute on #Column in front of a getter.
The problem is that it doesn't work. My little test program adds a property for hbm2ddl.auto to "create", so that it drops everything and re-creates it; the configuration also echoes the generated SQL. With #Column in front of the getter, not the field, the generated field is VARCHAR(255), same as without #Column.
When I move #Column to be in front of the field instead, it is created with the designated length of 25.
Is this a bug, or am I missing something (else) about the configuration of Hibernate fields with annotations? Unfortunately I don't want the other attributes mentioned in the docs, and I would think it strange that you had to specify one of those to get length recognized anyway.

If you want to use mixed access mode (i.e. use annotations on fields for some properties and on getters for others) you have to take a few extra steps.
First, set the default access type for the entity. For example, this will set the default access type to FIELD:
#Entity
#Access(AccessType.FIELD)
public class MyEntity { … }
Then for the properties that you want to annotate on the getters, explicitly set the access type to PROPERTY:
#Access(AccessType.PROPERTY)
#Column(length=25)
public String getFirstName() { return firstName; }

Related

How to create a single add method for different entities?

How can I create a single add method that will insert different entities in the jdbc? I want to use annotations and reflection for that.
I have created 2 annotations:
(...)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
and
(...)
public #interface Table {
String name();
}
Suppose we have 2 entities/models/etc. : Client and Waiter. For both we should make 2 add methods, each with own INSERT.
If we have like 4 entities, we should have 4 add methods for each entity. Instead of having 4 add methods, how can I make just 1 add method? and by using the annotations and the reflection.
Eg:
#Table(name = "Table_Client")
public class Client{
#Column(name = "ID", isPrimaryKey = true)
private long id;
#Column(name = "FULL_NAME")
private String name;
}
#Table(name = "Table_Waiter")
public class Waiter {
#Column(name = "FULL_NAME", isPrimaryKey = true)
private String name;
#Column(name = "AGE")
private int age;
}
case: db.add(Client c1) => add to the database in the table Table_Client the client c1
db.add(Waiter w1) => add to the database in the table Table_Waiter the waiter w1
and so on...
My idea is to take the class of the given object and scan it for the TYPE annotation to get the table's name. Then, take all the field's annotations and make a dynamic INSERT INTO VALUES query, but the problem is that I can't actually do that, because I can't pass the object's arguments.
Another question: if this can be done, update and delete methods can follow the same path?
I cannot refrain from mentioning how many holes you may find in the road ahead. But judging from the comments, that's the path you want to explore.
First of all, regarding your existing code, you need to apply a retention meta-annotation to your annotations, Column and Table. For example:
#Retention(RetentionPolicy.RUNTIME)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
This meta-annotation will ensure that you can read your annotations at runtime through reflection.
Then, you need to inspect the class searching for these annotations at both class and field levels.
The Class class will have everything you need. You should know you can get it from any object by calling the getClass method. It contains a couple of important methods for what you are trying to achieve:
getAnnotation(Class c) will return the annotation if it exists, or null otherwise.
getDeclaredFields will return all declared class fields, even private ones.
At a field level, the Field class provides the following methods:
getAnnotation(Class c), same as above, will return the annotation if it exists, or null otherwise.
getType will return the class associated with the field
Now consider the following piece of code:
public static void inspectClass(Class<?> cls) {
Table t = cls.getAnnotation(Table.class);
if (t != null) {
System.out.print(t.name() + " --> ");
for (Field f: cls.getDeclaredFields()) {
Column c = f.getAnnotation(Column.class);
if (c != null) {
System.out.print(c.name()
+ " "
+ f.getType().getSimpleName()
+ (c.isPrimaryKey() ? " PK" : "") + ", ");
}
}
}
}
Applying this to your Client class, for instance, would return something like:
Table_Client --> ID long PK, FULL_NAME String,
Of course, this needs some work, but the idea is there.
EDIT:
To access values of an instance through reflection at runtime, for creating a dynamic INSERT statement, that could be done by calling get method on the Field class. When dealing with private fields though, it's necessary to tweak the privacy mode first:
f.setAccessible(true);
Object value = f.get(myInstance);

Insert attribute in table if it is not null using Hibernate

I have mapping class defined as:
#Table(name = "TEST_TABLE")
public class DBTestAccount
{
#Id
#Column(name = "UUID", nullable = false, length = 36)
private String uuid;
#Column(name = "REGION")
private String region;
#Column(name = "COUNTRY")
private String countryCode;
//getters and setters
}
Now I need to update the table. For that let's say I create following object:
DBTestAccount dbTestAccount = new DBTestAccount();
dbTestAccount.setUuid("testUUID");
dbTestAccount.setRegion("testRegion");
dbTestAccount.setCountryCode(null);
Now let's say initially in the table we have a record that has some value of COUNTRY. Inserting the above object will replace the value and make COUNTRY null. I want that it should update the data, but if the column is null, then it should ignore and do not update it. If it is non-null then it should update it. How to achieve this in hibernate? Is there an annotation to do so? If not then what is the possible solution (except using if - else). Can I create a custom annotation for this?
PS:
The underlying database is PostgreSQL.
The example you are describing can't be present in the database, because the object is not an entity yet, as it is created with new keyword and it isn't yet persisted in the database.
From your explanation, what I got, is that you want to save only changed attributes. For that purpose hibernate has the Dynamic Update annotation.

How to persist an enum with JPA?

I'm using MySQL and JPA.
I have an enum that has it's own table.
I have a field in an entity (entity1) that uses this enum. This field is annotated with: #Enumeration(EnumType.STRING).
1 - is it correct to persist this field in entity1 as a column in the db when it has it's own table?
2 - if I am using #JsonProperty on my other fields and answer to 1 is "yes", must I use #JsonProperty on the enum field too?
3 - what's the point in having the enum in a separate table?
Currently, with just the #Enumeration annotation and a column for the enum for entity1 I get error: was annotated as enumerated, but its java type is not an enum
1- How I addressed similar problem was as follows :
I defined the enum in a separate entity :
#Entity
#Table(name="CALC_METHOD")
public class CalculationMethod {
public CalculationMethod() {
super();
// TODO Auto-generated constructor stub
}
#Id
#Enumerated(EnumType.STRING)
#Column(name="METHOD_NAME")
private CalculationMethodId calcMethodID;
#Column(name="DISPLAY_TEXT")
private String displayName;
.
.
.
.
then I refered to it in another entity as follows :
#OneToOne
#JoinColumn(name="CALCULATION_METHOD",referencedColumnName="METHOD_NAME")
private CalculationMethod calculationMethod;
that way it's stored in a seprate table, yet referenced from another entity with no duplication ... the point here is that you can't map enum scoped variables so when I needed to store a display name for the enum value, I needed to make it a separate attribute as you see
3- why to store it in table? because from the java POV it was really an enum , and I want to apply some calculation methods polymorpically (like calculate some value in the refering entity using the calculation method, so I defined calculate() method for each calculation method , each with a different implementation then call it while calculating a whole) the I wanted it to be always read with the same value and display name from many places in the code , and If I want to modify the display name, it's done only # one place -thus consistency and maintainability-
2- it depends on the requirement and your json model
For your situation I normaly use an entity on BBDD for the ENUM like:
AuthenticationType
id, name, value : (0, CERT, Certificate)
Where name is the real ENUM and value is the text I want to represent on the views.
For that you need the following:
public enum AuthenticationTypeEnum{
CERT, PASS;
}
#Entity
#Table(name = "AuthenticationType")
public class AuthenticationType{
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
#Enumerated(EnumType.STRING)
private AuthenticationTypeEnum name; // REAL ENUM TYPE
#Column(name = "VALUE")
private String value;
....
}
#Entity...
class Authentication{
private String login;
...
#ManyToOne
private AuthenticationType type; // ENUM USE
...
}
In that way you can edit the value of your ENUM on BBDD without changing your code, for me this is one of the best options.
Hope this helps.
When you persist the entity use the cascade all on JPA to persist also the enum entity.
NOTE: On normal situations, the enums not change, so you set them only ones. They are a prerequisite to the application so they change on rare circumstances.

Validating field length by making use of Jooq and JPA's #Column annotation

I am trying to validate the data input by the user by making use of JSR 303 validations. One validation that I am trying to implement is to check that the size of the inputted value for each field does not exceed the maximum size of the corresponding column.
In order to map a field to a database column I am making use of JPA's #Column annotation as follows:
#ComplexValidation
public class Person {
#Column(table = "PERSON_DETAILS", name = "FIRST_NAME")
private String firstName;
}
The #ComplexValidation annotation on the Person class, is a JSR 303 custom constraint validator that I am trying to implement, which basically tries to carry out the following steps:
Retreives all fields in the class annotated with #Column annotation
It extracts the table name from the annotation and uses it to load the corresponding JOOQ generated class representing the table
It extracts the field name from the annotation and uses it to load the data type and size for the corresponding column
Is there any way in Jooq where I can retrieve the Jooq Generated class based on the table name? My first attempt can be found below, however it does not work since table(tableName) returns an SQLTable not a TableImpl object:
Column columnAnnotation = field.getDeclaredAnnotation(Column.class);
if (columnAnnotation != null) {
String tableName = columnAnnotation.table();
String fieldName = columnAnnotation.name();
TableField tableField = (TableField) ((TableImpl) table(tableName)).field(fieldName);
int columnLength = tableField.getDataType().length();
if (fieldValue.length() > columnLength) {
constraintValidatorContext
.buildConstraintViolationWithTemplate("exceeded maximum length")
.addPropertyNode(field.getName())
.addConstraintViolation();
}
}
Any other suggestions are welcome :)
Assuming you only have one generated schema (e.g. PUBLIC), you can access tables from there:
Table<?> table = PUBLIC.getTable(tableName);
See Schema.getTable(String)

How to get key generated by Hibernate when I map a collection with #collectionId?

I am working on Quiz management. All mapping is done by Hibernate annotation for question to options. Question is an entity whereas all options are embedded object so I mapped option as follows :
QuestionMasterDTO's mapping for TabkidsMCQOptionMasterDTO :
#ElementCollection(fetch=FetchType.EAGER,targetClass=TabkidsMCQOptionMasterDTO.class)
#Fetch(FetchMode.SUBSELECT)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#CollectionTable(name="TABKIDS_MCQ_OPTION_MASTER",joinColumns={#JoinColumn(name="TMOM_QUESTION_ID")})
#GenericGenerator(name="hilo-gen",strategy="hilo")
#CollectionId(columns={#Column(name="TMOM_ID")},generator="hilo-gen", type=#Type(type="long"))
public Collection<IOptionMaster> getOptions() {
return options;
}
Where TabkidsMCQOptionMasterDTO is :
#Embeddable
public class TabkidsMCQOptionMasterDTO implements IOptionMaster {
private String optionText;
private boolean correct;
#Column(name = "TMOM_OPTION_TEXT")
public String getOptionText() {
return optionText;
}
#Column(name = "TMOM_IS_CORRECT")
public boolean isCorrect() {
return correct;
}
//setters omitted
}
Now in above mapping you can see I am using a generator i.e. hilo-gen and assigning a unique id to every option available in collection and that column name is 'TMOM_ID'.
This line :
#GenericGenerator(name="hilo-gen",strategy="hilo")
#CollectionId(columns={#Column(name="TMOM_ID")},generator="hilo-gen", type=#Type(type="long"))
Now when I fetch a question from database by using Hibernate criteria I am getting all options associated with the question but not getting unique option id i.e. TMOM_ID. How to get this id ??
Hibernate mainly uses two type of mapping Entity Type and Value Type.
Entity type means It will have its own existence in the world i.e. It must have a primary key.
Whereas Value type don't have its own existence this means value type always dependent on Entity type.
As your problem I can see Option does not have its won existence because it must always dependent of Question which is an entity .
So from my point of view if you want to access Option Id, Option must also be an entity type this means You have to use #Entity on top of TabkidsMCQOptionMasterDTO rather than making it as #Embeddable.
So here you have to use #OneToMany in your question master and from Other side in TabkidsMCQOptionMasterDTO you have to use #ManyToOne mapping.
I hope this will help to achieve what you want to get.

Categories

Resources