is #Temporal preferred to #Column columnDefinition? - java

Which is best practice?
#Column(name = "FOO", columnDefinition = "TIMESTAMP")
private Date foo;
Or
#Column(name = "FOO")
#Temporal(TemporalType.TIMESTAMP)
private Date foo;
The documentation suggests that using columnDefinition is non-portable ...

The documentation suggests that using columnDefinition is non-portable
...
That's true. columnDefinition specified the SQL data type that will be used. This data type may however not be available in all RDBMS. In JPA, it's the JPA provider's duty to figure out what SQL works on what DB. You can specifiy part of that configuration, but you will always risk breaking support for some databases.
#Temporal on the other hand is an abstraction that is part of the JPA standard. Every JPA provider must be able to map the different types of #Temporal to different SQL types for all supported databases.

Related

Spring JPA, Eclipselink and Auditing

I have been configuring Spring auditing for my entity classes. Using annotations, I have something like this:
#CreatedDate
#NotNull
private Date createdDate
#CreatedBy
#NotNull
private User createdBy
The createdBy field is being set correctly, however persisting the object fails with a null createdDate. I am guessing that this may be related to type conversion for Eclipselink?
#Temporal annotation is available since the release of JPA 1.0. #Temporal solves the one of the major issue of converting the date and time values from Java object to compatible database type and retrieving back to the application.
#Column(name = "XDATE")
#Temporal(TemporalType.DATETIME)
private Date xDate; //java.util.Date
I hope this will resolve your problem. For more info please refer this link

columnDefinition="DATE DEFAULT SYSDATE", don't work

I am using this code:
#Column(name = "FECHA_CREACION_TIMESTAMP",columnDefinition="DATE DEFAULT SYSDATE", insertable=false)
#Temporal(TemporalType.TIMESTAMP)
private Date fechaCreacionTimestamp;
But when I insert some data to the table, it doesn't have the date just have the date like null.
Updated Answer: (Now that I know that you are using Oracle)
What you need is to make sure that the column doesn't get included in the insert statements. For that, you need to update your #Column annoation like:
#Column(..other properties.., insertable = false)
See here for more details.
Now, you also need to make sure that the generated value is available in your domain object after you perform the insert. If you are using Hibernate, and do not mind using Hibernate annotations, you can put the following annotation on your field.
#org.hibernate.annotations.Generated(org.hibernate.annotations.GenerationTime.INSERT)
Hibernate will automatically perform the required select query for you.
If you are not using Hibernate, you will need to do a select yourself to fetch the generated value after performing the insert. JPA doesn't have anything to specify that this should be done automatically.
you can use something like :
#Column(name = "FECHA_CREACION_TIMESTAMP",columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP", insertable=false)
#Temporal(TemporalType.TIMESTAMP)
private Date fechaCreacionTimestamp;

JPA one field depends on another field in same table

I have a Class having two fields
#Column(name = "transactionDate", nullable = true)
private Date transactionDate;
#Column(name = "nextTransactionDate", nullable = true)
private Date nextTransactionDate
--
Both fields can be null. But if transactionDate is not null, then nextTransactionDate must not be null. How can I implement the above relationship between the fields using JPA? Any code snippet, link would be appreciated.
Note: I am using JPA not Hibernate.
You could validate this in your object model or application instead of through database constraints. You may also be able to define your own check constraint in the database through your own DDL script.
It's better to code the biz logic in DAO or service layer instead of defining it in ORM level. JPA and ORM tools is just designed to resolve persistence stuff not biz logic stuff.

JPA: how do I persist a String into a database field, type MYSQL Text

The requirement is that the user can write an article, therefore I choose type Text for the content field inside mysql database. How can I convert Java String into MySQL Text
Here you go Jim Tough
#Entity
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long userId;
private String title;
private String content;
private Integer vote;
//Constructors, setters, getters, equals and hashcode
}
In my MYSQL database, content is type Text. I was hoping that there would be something like this java.sql.Text, since java.sql.Blob is an actual type, but sadly, that does not exist
Since you're using JPA, use the Lob annotation (and optionally the Column annotation). Here is what the JPA specification says about it:
9.1.19 Lob Annotation
A Lob annotation specifies that a
persistent property or field should be
persisted as a large object to a
database-supported large object type.
Portable applications should use the
Lob annotation when mapping to a
database Lob type. The Lob annotation
may be used in conjunction with the
Basic annotation. A Lob may be
either a binary or character type. The
Lob type is inferred from the type of
the persistent field or property, and
except for string and character-based
types defaults to Blob.
So declare something like this:
#Lob
#Column(name="CONTENT", length=512)
private String content;
References
JPA 1.0 specification:
Section 9.1.19 "Lob Annotation"
With #Lob I always end up with a LONGTEXTin MySQL.
To get TEXT I declare it that way (JPA 2.0):
#Column(columnDefinition = "TEXT")
private String text
Find this better, because I can directly choose which Text-Type the column will have in database.
For columnDefinition it is also good to read this.
EDIT: Please pay attention to Adam Siemions comment and check the database engine you are using, before applying columnDefinition = "TEXT".
for mysql 'text':
#Column(columnDefinition = "TEXT")
private String description;
for mysql 'longtext':
#Lob
private String description;

Override Hibernate Annotations

I am developing a Java Application that uses Hibernate and is connected to an Oracle instance. Another client is looking to use the same application, but requires it run on MS SQL Server. I would like to avoid making changes to the existing annotations and instead create a package of xml files that we can drop in depending on the environment.
One way to do this is using JPA XML configuration to override the existing class annotations. However, JPA does not support generic generators, which is a requirement due to the structure of our legacy database. The other way that I am looking into is to use Hibernate XML configs to remap entire classes and have access to the generator xml tag. This solution has some issues though:
Hibernate does not allow you to selectively override entity members
Hibernate does not allow you to re-map the same class (e.g. org.hibernate.AnnotationException: Use of the same entity name twice)
Does anyone have any experience with overriding annotations using Hibernate XML Configuration files or is JPA the only way to go?
Update with an Example
In Oracle, Sequences are used to generate unique IDs when inserting new records into the database. An id would then be annotated in the following manner:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.SEQUENCE)
#SequenceGenerator(name="EXAMPLE_ID_GEN", sequenceName="SEQ_EXAMPLE_ID")
#Column(name = "EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
However, MS SQL Server does not have the concept of Sequences (Ideological differences). Therefore, you could use a table generator to simulate sequences.
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN", strategy=GenerationType.TABLE)
#TableGenerator(name="EXAMPLE_ID_GEN", tableName="SEQUENCE", valueColumnName="VALUE", pkColumnName="SEQUENCE", pkColumnValue="EXAMPLE_ID")
public String getExampleId() {
return this.exampleId;
}
Two different configurations for two different types of databases. Keep in mind that this is a legacy database and that we aren't going to rewrite our application to support SQL Server identities, the native id generator for SQL Server (which would also require a different annotation).
To alleviate this, I have looked into using Hibernate's #GenericGenerator and point it to a class of my own creation that models org.hibernate.id.SequenceGenerator (or something similar) and also customize the structure of the table by extending org.hibernate.id.TableStructure.
Back to my original question - is any of this possible with an XML override?
How I Solved this Problem
So, in the end, I found that JPA and Hibernate did not provide the out-of-box functionality that I was looking for. Instead, I created a custom generator that checked the database dialect and set the TableStructure appropriately. As I explored all options, I ended up using Hibernate's #GenericGenerator annotation. This is an example of the Id generation annotation:
#Id
#GeneratedValue(generator="EXAMPLE_ID_GEN")
#GenericGenerator(name = "EXAMPLE_ID_GEN", strategy="com.my.package.CustomIdGenerator", parameters = {
#Parameter(name = "parameter_name", value="parameter_value")
})
public String getExampleId() {
return this.exampleId;
}
This solution necessitates that each Hibernate entity be modified with the new Id generator.
I think that if you don't use AnnotationConfiguration when configuring your SessionFactory, the annotations will be omitted.
So, use Configuration.
For your generator problem (for which the solution would normally be "use the native generator" but doesn't work for you due to working with a legacy db), you could probably extend the SQLServerDialect and override the getNativeIdentifierGeneratorClass to return a (possibly custom) generator that does what you need for your legacy db.
I have come across the need to mix-n-match legacy with new schemas/databases before in a Grails (GORM) application, which of course is running Hibernate 3 underneath.
Would not say "you're doing it wrong" - but I would keep the JPA #Annotations to the very basics like #Entity and #Column and leave it to the Hibernate dialect, which is also specified in the XML configuration file.
You might experiment with subclassing the Oracle10gDialect with one that assigns a sequence generator to all tables, versus a Sybase one which does not.
Please see this post on how to implement this.
UPDATE:
What james and I are suggesting (almost in the same minute) is to setup multiple persistence-unit sections of your persistence.xml file.
This allows one to use #Entity and #Id without supplying details in the class. The details come in the hibernate.dialect property. I suggested subclassing Oracle10gDialect (and james the SQLServerDialect) - those would do the choosing as to the table naming, id generator strategy, etc.
See --> https://forum.hibernate.org/viewtopic.php?f=1&t=993012
If you rewrite the annotations in HBM XML files, you could maintain two sets of such XML and pick which ones to use via Hibernate's mapping directives. I've done this in Hibernate Core, but not in a J2EE/JPA environment so I don't know if there are any gotchas in that respect.
The biggest downside is it likely will be a lot of work to remove all your annotations and rebuild them in XML.
I would say that if your annotations are database specific, you're doing it wrong.
In my case:
Rack and Slot are entities having custom ID Generators. I am using unidirectional one-to-one mapping. Dimension table will hold the data with a Autogenerated Custom ID as foreign key for multiple tables (Rack and Slot for example here).
And my schema looks like this : Rack ------> Dimension <-----------Slot
where Dimension will hold the data for Rack and Slot table with Generated ID.
Here the concern is that when i am saving the data like this:-
Rack rack = new Rack(params);
Dimension dim = new Dimension(params);
rack.setDimension(dim);
session.save(rack);
Data is being saved successfully with same Autogenerated ID in Rack and Dimension Tables.
But when I am saving the data for Slot table :
Slot Slot = new Slot(params);
Dimension dim = new Dimension(params);
slot.setDimension(dim);
session.save(slot);
it is showing error message as:-
attempted to assign id from null one-to-one property: rack
Can I pass the dynamic property name as "slot" when saving the data for Slot and Dimension and "rack" when saving the data for Rack and Dimension.
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
Rack.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Slot.java
#Entity
#Table(name="tablename")
#GenericGenerator(name = "customseq", strategy = "CustomIdGenerator")
public class Rack {
#Id
#GeneratedValue(generator = "customseq")
#Column(name = "uni_id")
private String id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Dimension dimension;
// Getters and Setters
}
Dimension.java
public class Dimension implements Serializable{
#Id
#Column(name = "systemid")
#GeneratedValue(generator = "foreign")
#GenericGenerator(name = "foreign", strategy = "foreign", parameters = {
#Parameter(name = "property", value = "slot"),
#Parameter(name = "property", value = "rack")})
private String systemid;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Rack rack;
#OneToOne(mappedBy = "dimension", fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Slot slot;
// Getters and Setters
}

Categories