Specify NULLS LAST in #OrderBy of #OneToMany mapped Collection - java

I have a mapped entity like this:
#OneToMany(targetEntity = Child.class)
#JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID")
#OrderBy("orderNumber")
private List<Child> children;
I would like to specify NULLS LAST in #OrderBy annotation of my mapped collection.
I am using Oracle database, which considers NULL values larger than any non-NULL values.
The problem is in my integration test, which uses h2 database and it seems the NULL values are evaluated differently.
So far, I came up with a hack to use nvl2() function inside of the #OrderNumber like this:
#OrderBy("nvl2(orderNumber, orderNumber, 100000000)")
This hack works, but it seems nasty and I don't like the idea that I have this code there just because of the integration tests. As I mentioned above, Oracle returns the rows in correct order by default, so the basic #OrderBy(orderNumber) without handling nulls works good. On the other hand, I want to have it tested in case the app will use different database.
Is there any way how to solve this issue in a better way?

To enable Oracle compatibility mode in H2 you need to append ;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH to JDBC URL as suggested in documentation:
https://h2database.com/html/features.html#compatibility
The DEFAULT_NULL_ORDERING setting changes the default ordering of NULL values (when neither NULLS FIRST nor NULLS LAST are used). There are four possible options, LOW is default.
This setting can be used separately without this compatibility mode if you don't need it.

Related

Hibernate/JPA property mapping result of database function with other columns

I have a situation where I'm using Hibernate (5.2.16) to map a table and one of the column values is constructed via a database function that takes the values of two other properties.
For some background, this is a SDE spacial table with a ST_GEOMETRY column. As far as I can tell, this isn't compatible with the two types of spacial APIs supported by Hibernate, but even if it was, I'm not doing any spacial manipulation, so I don't really need them, I just want to insert and update the geometry column.
I have absolutely no control over the structure of the table because it's dictated by another group using another tool (GIS).
Things I've tried:
Using a Hibernate UserType. The problem with this is that I only see a way to get and set the value with a PreparedStatement, without the ability to dictate actual SQL used.
basic-custom-type
Using a Hibernate ColumnTransformer. This gives me direct control over the SQL used, but I can't use the values of two other properties in the SQL.
mapping-column-read-and-write
#Column(name="LATITUDE")
private BigDecimal latitude;
#Column(name="LONGITUDE")
private BigDecimal longitude;
#ColumnTransformer(
read="sde.st_astext(shape)",
write="sde.st_transform(sde.st_point(LONGITUDE,LATITUDE, 4326), 3857)"
)
#Generated(value=GenerationTime.ALWAYS)
#Column(name="SHAPE")
private String shape;
I get:
org.hibernate.AnnotationException: #WriteExpression must contain exactly one value placeholder ('?') character: property [shape] and column [SHAPE]
I've looked at Generated columns, but those are for values generated by the database.
mapping-generated
I've looked at Formula columns, but those are for values calculated and usable in Java, but aren't inserted or updated. mapping-column-formula
#Formula(value="sde.st_astext(shape)")
private String shape;
It's useful for some things, but I can't insert or update this.
I'm hoping that I've missed something. At this point I'm considering non-Hibernate/JPA solutions. This would be relatively easy with raw SQL and JDBC, but the rest of the table would be annoying and not match the rest of my code. I'd also have to do my own dirty checking and stuff.
You can use the Hibernate database generated value. It allows you to call database functions to generate entity properties.
This is a database-specific answer, but given your GIS problem domain and the dominance of PostGIS it may be relevant (if you use PostgreSQL and your DBA is OK with an upgrade).
PosgreSQL 12 introduces generated columns which you could define using something similar to the following:
#Column(columnDefinition = "GEOMETRY GENERATED ALWAYS AS st_transform(st_point(LONGITUDE,LATITUDE, 4326), 3857)) STORED")

Hibernate: custom entity name

Let's say I have an entity with a very long name:
#Entity
public class SupercalifragilisticexpialidociousPanda
{
...
}
Using Hibernate to persist it to a Postgres DB works flawlessly. Oracle, however, doesn't allow for table/column/index names longer than 30 characters.
That should be easy to fix, since i can just specify the table name manually, like this:
#Entity
#Table(name="SuperPanda")
public class SupercalifragilisticexpialidociousPanda
{
...
}
Now everything is back to working perfectly... except that any references I have to the entity in other tables still use the long class name ("SupercalifragilisticexpialidociousPanda") instead of the short table name ("SuperPanda").
For instance, if the entity has an embedded ElementCollection, like this:
#ElementCollection
private Set<String> nicknames;
Hibernate will try to create a DB like this: create table SupercalifragilisticexpialidociousPanda_nicknames, which will naturally cause an ORA-00972: identifier is too long error.
The same thing also happens for #OneToOne associations, where the lookup column would be called something like supercalifragilisticexpialidociousPanda_uuid, which also fails with oracle.
Now, one option would be to add a #CollectionTable(name="SuperPanda_nicknames") and #Column(name="...") annotation manually to every field that references this entity, but that's a lot of work and really error-prone.
Is there a way to just tell Hibernate once to use the short name everywhere a reference to the entity is required?
I also tried setting the entity name, like this:
#Entity(name="SuperPanda")
#Table(name="SuperPanda")
public class SupercalifragilisticexpialidociousPanda
{
...
}
... but it doesn't fix the issue.
What does one normally do in such a case?
Usually people give names for each database thing (table, column, index) by themselves. Letting Hibernate decide for you can lead to problem in future when you decide to refactor something.
All reference can be configured one way or another to use names you decide to use.
Ask specific question in case you can figure out the way to do it yourself.

Grails. Hibernate lazy loading multiple objects

I'm having difficulties with proxied objects in Grails.
Assuming I've got the following
class Order {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="xxx", joinColumns = {#JoinColumn(name = "xxx")}, inverseJoinColumns = {#JoinColumn(name = "yyy")})
#OrderBy("id")
#Fetch(FetchMode.SUBSELECT)
private List<OrderItem> items;
}
class Customer {
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "xxx",insertable = false, nullable = false)
private OrderItem lastItem;
private Long lastOrderId;
}
And inside some controller class
//this all happens during one hibernate session.
def currentCustomer = Customer.findById(id)
//at this point currentCustomer.lastItem is a javassist proxy
def lastOrder = Order.findById(current.lastOrderId)
//lastOrder.items is a proxy
//Some sample actions to initialise collections
lastOrder.items.each { println "${it.id}"}
After the iteration lastOrder.items still contains a proxy of currentCustomer.lastItem. For example if there are 4 items in the lastOrder.items collection, it looks like this:
object
object
javassist proxy (all fields are null including id field). This is the same object as in currentCustomer.lastItem.
object
Furthermore, this proxy object has all properties set to null and it's not initialized when getters are invoked. I have to manually call GrailsHibernateUtils.unwrapIdProxy() on every single element inside lastOrder.items to ensure that there are no proxies inside (which basically leads to EAGER fetching).
This one proxy object leads to some really weird Exceptions, which are difficult to track on testing phase.
Interesting fact: if I change the ordering of the operations (load the order first and the customer second) every element inside lastOrder.items is initialized.
The question is: Is there a way to tell Hibernate that it should initialize the collections when they are touched, no matter if any elements from the collection is already proxied in the session?
I think what's happening here is an interesting interaction between the first level cache (stored in Hibernate's Session instance) and having different FetchType on related objects.
When you load Customer, it gets put in to the Session cache, along with any objects that are loaded with it. This includes a proxy object for the OrderItem object, because you've got FetchType.LAZY. Hibernate only allows one instance to be associated with any particular ID, so any further operations that would be acting on the OrderItem with that ID would always be using that proxy. If you asked the same Session to get that particular OrderItem in another way, as you are by loading an Order containing it, that Order would have the proxy, because of Session-level identity rules.
That's why it 'works' when you reverse the order. Load the Order first, it's collection is FetchType.EAGER, and so it (and the first level cache) have fully realized instances of OrderItem. Now load a Customer which has it's lastItem set to one of the already-loaded OrderItem instances and presto, you have a real OrderItem, not a proxy.
You can see the identity rules documented in the Hibernate manual:
For objects attached to a particular Session... JVM identity for database identity is guaranteed by Hibernate.
All that said, even if you get an OrderItem proxy, it should work fine as long as the associated Session is still active. I wouldn't necessarily expect the proxy ID field to show up as populated in the debugger or similar, simply because the proxy handles things in a 'special' way (ie, it's not a POJO). But it should respond to method calls the same way it's base class would. So if you have an OrderItem.getId() method, it should certainly return the ID when called, and similarly on any other method. Because it's lazily initialized though, some of those calls may require a database query.
It's possible that the only real problem here is simply that it's confusing to have it so that any particular OrderItem could be a proxy or not. Maybe you want to simply change the relationships so that they're either both lazy, or both eager?
For what it's worth, it's a bit odd that you've got the ManyToMany relationship as EAGER and the ManyToOne as LAZY. That's exactly the reverse of the usual settings, so I would at least think about changing it (although I obviously don't know your entire use case). One way to think about it: If an OrderItem is so expensive to fetch completely that it's a problem when querying for Customer, surely it's also too expensive to load all of them at once? Or conversely, if it's cheap enough to load all of them, surely it's cheap enough to just grab it when you get a Customer?
I think you can force eager loading this way or using
def lastOrder = Order.withCriteria(uniqueResult: true) {
eq('id', current.lastOrderId)
items{}
}
or using HQL query with 'fetch all'

Changing from Collection to SortedSet

I'm changing a Collection to a SortedSet because I need it to always be in the same consistent order that they were created in. I've changed my model property from
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contentId")
private Collection<Footnote> footnoteCollection;
to
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contentId")
private SortedSet<Footnote> footnoteSortedSet;
and all relevant functions so Netbeans no longer shows any errors. When I run the app I get the error: Exception Description: Could not load the field named [footnoteSortedSet] on the class [class com.mysite.cmt.model.Content_]. Ensure there is a corresponding field with that name defined on the class.
Since I've just changed this properly and relaunched my app I'm struggling to figure out why it's saying it's not set...
The error you are getting seems to be coming from the JPA metamodel. I assume you are generating this in some way, if you don't use the metamodel in Criteria, then you don't need this and the error will go away.
The issue is that JPA only allows the collection interfaces, Map, List, Set, Collection. So, while you could use a SortedSet in your new instances, object read from the database will use a special lazy List implementation.
In EclipseLink, you can use a SortedSet if you mark the mapping as EAGER.
I think the metamodel error was fixed, try the latest release.
SortedSet javadoc to the rescue:
All elements inserted into a sorted set must implement the Comparable interface (or be accepted by the specified comparator).
Almost certainly, Footnote does not implement Comparable

Does #JoinTable has a property of "table" or not?

The following is copied from hibernate's document. (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e2770)
#CollectionOfElements
#JoinTable(
table=#Table(name="BoyFavoriteNumbers"),
joinColumns = #JoinColumn(name="BoyId")
)
#Column(name="favoriteNumber", nullable=false)
However, when I put this in practice, I just found that #JoinTable has no "table" property, instead it has a "name" property to specify the table name. But I need "table" property to specify indexes.
What's going on here? I'm almost driven crazy!
No, it doesn't, this sample is not accurate. Just in case, the #IndexColumn annotation that you see in this sample has nothing to do with a database index, it is used to store the index number of an element in an indexed collection. But I guess you're aware of that.
Actually, I'd suggest to raise a Jira issue specifying your use case and your database dialect (it seemts that generating an index on the FK works with some dialects, like MySQL, but doesn't with say Oracle).

Categories

Resources