Custom sequence generator for Hibernate 5 - java

I want to create a custom sequence generator in Hibernate 5 to create a sequence per table in Postgresql. Using Hibernate 4 I did the following in my dialect:
/**
* Get the native identifier generator class.
*
* #return TableNameSequenceGenerator.
*/
#Override
Class<?> getNativeIdentifierGeneratorClass() {
TableNameSequenceGenerator
}
/**
* Creates a sequence per table instead of the default behavior of one sequence.
* From <a href='http://www.hibernate.org/296.html'>http://www.hibernate.org/296.html</a>
*/
static class TableNameSequenceGenerator extends SequenceGenerator {
/**
* {#inheritDoc}
* If the parameters do not contain a {#link SequenceGenerator#SEQUENCE} name, we
* assign one based on the table name.
*/
#Override
void configure(final Type type, final Properties params, final Dialect dialect) {
Boolean sequencePerTable = Holders.config.getProperty(SEQUENCE_PER_TABLE, Boolean, true)
if (sequencePerTable) {
if (!params.getProperty(SEQUENCE)) {
String tableName = params.getProperty(TABLE)
String schemaName = params.getProperty('schemaName')
if (schemaName) {
params.setProperty(SCHEMA, schemaName)
}
if (tableName) {
params.setProperty(SEQUENCE, "seq_${tableName}")
}
}
}
super.configure(type, params, dialect)
}
}
You can see the full code here: https://github.com/kaleidos/grails-postgresql-extensions/blob/master/src/main/groovy/net/kaleidos/hibernate/PostgresqlExtensionsDialect.groovy#L53
I'm trying to migrate to Hibernate 5 but I don't know how to configure the previous behavior. I've modified the code to extends from SequenceStyleGenerator because now SequenceGenerator has been deprecated but my code is never executed. I think this has to do with the fact that the method getNativeIdentifierGeneratorClass has also been deprecated.
I've been looking for a way to create a custom sequence but all examples I've found are focused on annotate my domain class with the sequence generator. What I'm looking for is a way to define all the sequences in a global way.

As Graeme noted (https://github.com/grails/grails-core/issues/10234) the default name of the sequence has changed in Hibernate 5, so adding
id generator: 'sequence', params: [sequence_name: 'book_seq']
to the mapping block does the trick. The problem is that it's necessary to add that to every domain class.
I'm still looking for a way to define that setting globally, maybe setting prefer_sequence_per_entity to true for every entity.
UPDATE: Finally we found a workaround to define a sequence per table globally. Just add the following to the file application.groovy:
grails.gorm.default.mapping = {
id generator: 'org.hibernate.id.enhanced.SequenceStyleGenerator', params: [prefer_sequence_per_entity: true]
}

Thanks a lot Ivan! I tried only this configuration, which worked too.
grails.gorm.default.mapping = {
id params: [prefer_sequence_per_entity: true]
}

Related

Validation for required boolean attributes doesn't work

I'm using OpenAPI 2.0 contract for my Rest service, written in Yaml, and I'm using swagger-codegen to generate the POJOs. I activated the useBeanValidation and performBeanValidation, so that the generated code (in JAVA) include the validation annotations...
In my contract I have a field of type boolean that I marked as required:
cancellationInd:
description: "indicates if this is a cancellation"
type: boolean
required: true
and the generated code is as follow:
/**
* indicates if this is a cancellation.
* #return cancellationInd
**/
#NotNull
#ApiModelProperty(required = true, value = "indicates if this is a cancellation")
public Boolean isCancellationInd() {
return cancellationInd;
}
public void setCancellationInd(Boolean cancellationInd) {
this.cancellationInd= cancellationInd;
}
The problem is that in my JsonREquest to the serviec, when I used
cancellationInd: null, it passes.... I was expecting some HttpStatus_400
because of the required field. Same thing when field is emtpy instead of null...
{
...
"timstamp": "2019-04-29T13:23:56.123-04:00",
"cancellationInd": "false",
...
}
It looks like the reason is that when the attribute is of type boolean, the generated one in java is Boolean (which is a wrapper, not primitive, and thus accepts a null)... am I right? Is this is a bug?
Any suggestions please?

Hibernate annotation for single select object (with where) instead of on-to-many collection

Currently we have a class that looks something like that (depersonalised and non-relevant parts removed):
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractTable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#OrderBy("CREATED_ON DESC")
private Set<MainTableState> states;
...
public MainTableState getActiveState(){
if(this.states == null || this.states.isEmpty()){
return null;
}
MainTableState latest = states.iterator().next();
// The reason we use this for-loop, even though we have the #OrderBy annotation,
// Is because we can later add states to this list, which aren't automatically ordered
for(MainTableState state : states){
if(state.getCreatedOn() != null && latest.getCreatedOn() != null &&
state.getCreatedOn().after(latest.getCreatedOn()){
latest = state;
}
}
return latest;
}
...
}
So currently it will retrieve all MainTableStates from the DB by default, and if we need the activeState we use the for-loop method. Obviously this is pretty bad for performance. Currently we don't use this list at all (the purpose was to have a history of states, but this has been postponed to the future), but we do use the getActiveState() method quite a bit, mostly to show a String inside of the MainTableState-class in the UI.
In addition, even if we would always use a TreeSet and keep it sorted so we won't need the loop but only need states.iterator().next() instead, it will still initialize the list of states. With some heavy performance testing we had more than 1 million MainTableState-instances when it crashed with an java.lang.OutOfMemoryError: GC overhead limit exceeded.
So, we want to change it to the following instead:
#Entity
#Table(name = "MAIN_TABLE")
public class MainTable extends AbstractEntity {
#???
private MainTableState activeState;
...
public MainTableStates getActiveState(){
return activeState;
}
...
}
So, my question, what should I put at the #??? to accomplish this? I'm assuming I need the #Formula or something similar, but how can I say to hibernate it should return a MainTableState object? I've seen #Formula being used with MAX for a date, but that was to get that date-property, not get an entire object based on that max date.
After #user2447161's suggestion I've used a #Where-annotation, which does indeed help to reduce the Collection size to 1 (sometimes), but I have two more related questions:
How to use #OnToMany and #Where but get a single object, instead of a list of objects of size one? Is this even possible? Here in a answer from December 2010 it is stated it isn't. Has this been fixed somewhere in the last six years?
How to deal with the random alias in the where clause? I could do something like this:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "mainTable")
#Where(clause = "CREATED_ON = (SELECT MAX(mts.CREATED_ON) FROM MAIN_TABLE_STATES mts WHERE mts.FK_MAIN_ID = ???.MAIN_ID)")
private Set states; // TODO Get single object instead of collection with size 1
The problem with is that ??? is a random alias generated by hibernate (sometimes it's this_, sometimes it's something along the lines of mainTable_1_, etc.). How to set this alias for the entire query to the DB to use it here? I also tried MAIN_TABLE.MAIN_ID instead which doesn't work, and with no alias it also doesn't work because it uses the MainTableState-alias instead of MainTable-alias (like this below).
from
MAIN_TABLE this_
left outer join
MAIN_TABLE_STATUSES mainstat2_
on this_.main_id=mainstat2_.fk_main_id
and (
mainstat2_.created_on = (
SELECT
MAX(mts.created_on)
FROM
MAIN_TABLE_STATUSES mts
WHERE
-- mainstat2_.main_id should be this_.main_id instead here:
mts.fk_main_id = mainstat2_.main_id
)
)
Well, regarding your question #2, as it looks like you need a quick solution with minimal impact in your existing code, this may be acceptable: you can use an Interceptor to deal with the alias and generate the right sql statement. Do this:
use a unique string as alias placeholder in your #Where clause, for instance:
...WHERE mts.FK_MAIN_ID = ${MAIN_TABLE_ALIAS}.MAIN_ID...
if your application doesn't have one yet, create an Interceptor class extending EmptyInterceptor and configure it as a SessionFactory interceptor
override the onPrepareStatement method to replace the placeholder with the alias found after 'from MAIN_TABLE' with something like this:
public String onPrepareStatement(String sql) {
String modifiedSql = sql;
if (sql.contains("${MAIN_TABLE_ALIAS}")) {
String mainTableAlias = findMainTableAlias(sql);
modifiedSql = sql.replace("${MAIN_TABLE_ALIAS}", mainTableAlias);
}
return modifiedSql;
}
Be aware that this method will be called for every sql statement that hibernate generates in your application.
Additionaly, your #Where clause only works properly when a join is used, so you should set the fetch mode explicitly adding
#Fetch(FetchMode.JOIN)
to the states property to avoid that hibernate may use the select mode.

Generate sequence NexVal without execute select query

My class is not Entity there is code fragment
#SequenceGenerator(name="seqUniqueKeyGenerator",sequenceName="SEQ_UNIQUE_KEY",allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="seqUniqueKeyGenerator")
#Id
private Integer sequenceId;
public Integer getSequenceId() {
return sequenceId;
}
public void setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
}
public static void main(String[] args) {
UniqueKeyGenerator uniqueKeyGenerator = new UniqueKeyGenerator();
System.out.println(uniqueKeyGenerator.getSequenceId());
}
I want retrieve nextVal like this, is it possible?
You can consume nextVal as mentioned in this thread but you have to consider it is consumed by means of a SQL sentence, which means this is a solution coupled to database.
I don't know a way to consume nextVal in such way you are asking above.
We all know the default behaviour of Hibernate when using #SequenceGenerator - it increases real database sequence by one, multiple this value by 50 (default allocationSize value) - and then uses this value as entity ID.
From G. Demecki
This means that the ID id not generated in your Java-Program. It is created in your database. So you can't read it before it is acutally generated in the database. You can guess it using the formula described by G.Demecki but that is certainly not the way to go.
If you want the id of an entity just save it and read the id from the return value of save what should be the saved entity itself.

JAutodoc: getter comment template is merged with default comment behavior

I am trying to configure JAutodoc such that getter comments are generated containing only the #returns tag, like so:
/**
* #returns The non-null {#linkplain Foo foo} of this {#link IBar}.
*/
public Foo getFoo();
I have configured my getter template to produce this:
However, something must be wrong with my general JAutodoc settings, because what I get instead is a hybrid of my template and a comment parsed from the method name:
/**
* Get foo.
* #returns The non-null {#linkplain Foo foo} of this {#link IBar}.
*/
public Foo getFoo();
These are my settings:
I have removed the 'get' replacement from the replacements list, as well as unchecked the 'etter from field comment' setting as advised in this discussion, but it has not made a noticeable difference. I have also attempted to uncheck the 'Create comment from element name' setting, despite my example getter being part of an interface (in which case there is no element to get the comment from), but JAutodoc doesn't seem to care about that.
I have also tried restarting Eclipse after making each of these changes, in case that mattered. So far, nothing is working. It almost appears as if the comment behavior of getters is hard-coded. Can someone please shed some light on this?
TL;DR
JAutodoc, in its current form, can not do what you want it to do. This is because you are asking for incomplete Javadocs.
Details
(This was fun, and I hope you appreciate the effort :-)
This is a case where you are asking JAutodoc to create incomplete Javadocs. i.e. you are asking for no documentation in the Javadoc. (I personally find the repetitiveness on simple getter annoying too BTW).
The steps that JAutodoc are going through internally is:
Applying your template, so the comment looks exactly as you want - for a moment in time.
This is the bit of code that applies the template. Using your example, the member parameter below is your getFoo method and, as there is no existing comment in your code when you first apply the auto-comment, so the jdi parameter is empty (jdi.isEmpty() == true).
When the template is applied, text looks exactly as you want it too. text gets parsed just as any Javadoc comment would and returned.
From net.sf.jautodoc.source.JavadocCreator:
public JavadocInfo applyTemplate(final IMember member, final JavadocInfo jdi) throws Exception {
final JavadocInfo templateJdi = new JavadocInfo();
final String text = JAutodocPlugin.getContext().getTemplateManager().applyTemplate(member,
config.getProperties());
if (text != null && text.length() > 0) {
templateJdi.parseJavadoc(text);
}
return jdi.isEmpty() ? templateJdi : jdi.merge(templateJdi);
}
Now the JavadocInfo that was returned from applyTemplate is passed to createJavadoc. In createJavadoc the code checks if there is a comment (excluding the #params, #return, etc). As there is not any, it tries to insert some automatically from the information available. Simplistically, it just un-camel-cases the name of the method and makes that the comment.
From net.sf.jautodoc.source.JavadocCreator
public String createJavadoc(final IMethod method, final String indent, final String lineSeparator,
final JavadocInfo jdi) throws JavaModelException {
final List<String> text = jdi.getComment();
if (text.isEmpty()) {
if (config.isCreateDummyComment()) {
if (method.isConstructor()) {
text.add(Constants.JDOC_CONSTRUCTOR);
}
else if (method.isMainMethod()) {
text.add(Constants.JDOC_MAIN);
}
else {
String comment = CommentManager.createComment(config, method.getElementName(),
CommentManager.METHOD, true, true, CommentManager.FIRST_TO_UPPER);
text.add(comment + Constants.DOT);
}
}
else {
text.add("");
}
}
else {
checkForDot(text);
}
Now the code that calls the above two methods is this:
From net.sf.jautodoc.source.SourceManipulator.addJavadoc(IMember):
if (config.isCreateDummyComment()) {
jdi = javadocCreator.applyTemplate(member, jdi);
}
newJavadoc = javadocCreator.createJavadoc((IMethod) member, indent, lineDelimiter, jdi);
As you can see from these code snippets, both applying your template and creating the comment part (that you don't want!) is controlled by the same if statement config.isCreateDummyComment(). That if statement connects to the Create comment from element name option.
Examples
This issue is not happening because the method is a getter, but applies everywhere. Assume you have this bit of code:
/**
* #param myFirstParam this is important and I documented it
*/
public int doThisAndThat(int myFirstParam, int anotherParamHere) {
return 0;
}
And you apply JAutodoc to it (with Complete existing Javadoc) then you get:
With Create comment from element name unset:
/**
*
*
* #param myFirstParam this is important and I documented it
* #param anotherParamHere
* #return
*/
public int doThisAndThat(int myFirstParam, int anotherParamHere) {
return 0;
}
With Create comment from element name set:
/**
* Do this and that.
*
* #param myFirstParam this is important and I documented it
* #param anotherParamHere the another param here
* #return the int
*/
public int doThisAndThat(int myFirstParam, int anotherParamHere) {
return 0;
}
Getting the source
I couldn't find the source on any of the usual suspects (github, etc), but it is available for download here: http://sourceforge.net/projects/jautodoc/files/jautodoc/1.13.0/
So you could, if you desired, edit the source and rebuild your plug-ins. Or file a feature request with the dev.
In Conclusion
JAutodoc, in its current form, can not do what you ask it to do. However, this is essentially by design, because if you say you want to Create comment from element name automatically (internally called Create Dummy Comment) then you want fully complete Javadoc to be created for you.
Finally, keep in mind if there is no comment, then nothing appears in the method summary table and your generated Javadocs simply look incomplete.

How to exclude a column during INSERT with dbunit to HSQLDB

I export data from MS SQLServer to an xml file, then use that dataset in running unit tests that require database. I use dbunit maven plugin for it.
Unfortunately for me, not all columns in some tables are mapped in my Entity classes.
As an example, say, we have a table called 'member'.
Member table has three columns: memberid, membername, memberrank.
When I do an export, I get all three columns exported.
But in my MemberEntity class, I only map memberid and membername, because I do not need memberrank in my application. So I would have the MemberEntity looking like this:
#Entity
#Table(name = "member")
public class MemberEntity {
#Id
#GeneratedValue()
#Column(name = "memberid", nullable = false)
private Integer memberid;
#Column(name = "membername", nullable = false)
private String membername;
...
}
Then, I try to insert dataset into HSQLDB before a test case:
IDatabaseConnection conn = new DatabaseConnection(((SessionImpl) (entityManager.getDelegate())).connection());
IDataSet dataset = new XmlDataSet(
resourceLoader.getResource("classpath:dataset.xml").getInputStream());
conn.getConfig().setProperty("http://www.dbunit.org/properties/datatypeFactory", new MsSqlDataTypeFactory());
DatabaseOperation.CLEAN_INSERT.execute(conn, dataset);
At this point, I get an exception saying the column MemberRank does not exist. It says something like the following:
org.dbunit.dataset.NoSuchColumnException: MEMBER.MEMBERRANK - (Non-uppercase input column: memberrank) in ColumnNameToIndexes cache map. Note that the map's column names are NOT case sensitive.
When I remove the column from the dataset, all is well. If I add in the memberRank mapping to my Entity class, again, all goes well.
But I cannot add the column mapping into my Entity class. Is there an easy way (other than removing the column and the associated data from the exported dataset manually) of excluding that column from being (attempted to be) added in when I do INSERT?
In hibernate every non static non transient property (field or method depending on the access type) of an entity is considered persistent, unless you annotate it as #Transient.
for example,
#Transient
public int counter; //transient property
private String firstname; //persistent property
The methods and fields annotated as #Transient will be ignored by the entity manager.See here for more information.
Maybe this answer comes a little bit late, but I've just run into a similar problem and wrote the following method to solve it (I'm using dbUnit 2.5.0). Hope it helps somebody.
/**
* Generates a new data set with the columns declared in the
* "excludedColumns" map removed.
*
* #param src
* Source data set.
* #param excludedColumns
* Map of table names and column names. Columns in this map are
* removed in the resulting data set.
* #return Data set with the columns declared in the "excludedColumns" map
* removed. Tables that are not specified in the "excludedColumns"
* map are left untouched.
* #throws DataSetException
*/
public static IDataSet filterDataSet(IDataSet src,
Map<String, Set<String>> excludedColumns) throws DataSetException {
if (excludedColumns == null) {
return src;
}
ArrayList<ITable> tables = new ArrayList<ITable>(
src.getTableNames().length);
for (String tableName : src.getTableNames()) {
if (excludedColumns.containsKey(tableName)) {
ITable filteredTable = DefaultColumnFilter
.excludedColumnsTable(
src.getTable(tableName),
excludedColumns.get(tableName).toArray(
new String[0]));
tables.add(filteredTable);
} else {
tables.add(src.getTable(tableName));
}
}
return new DefaultDataSet(tables.toArray(new ITable[0]),
src.isCaseSensitiveTableNames());
}
The core of the method is DefaultColumnFilter. I'm using a commodity static method here, but an instance of DefaultColumnFilter gives a lot of flexibility.
I wonder if there is a more straight forward way of doing this.

Categories

Resources