PostgreSQL function used in QueryDSL is not working, returns ERROR: syntax error at or near "." - postgresql

This is my very first question on stackoverflow, so sorry in advance if anything is not as precise as it should be
In my project, I use Hibernate (as ORM framework) with QueryDSL lib, PostgreSQL as a database.
Basically, I need to check the size of a list 'arr', which is a property of some 'X' class, so I googled and found a way to use postgres functions with querydsl as follows (before you ask, I can't use native queries by the requirements):
BooleanBuilder builder = new BooleanBuilder();
builder.and(Expressions.booleanTemplate("function('array_length', {0})", qX.arr)
.castToNum(Integer.class).gt(0));
Everything compiles fine, but when the repository method is being called, I get an error:
ERROR: syntax error at or near "." Position: ...
I checked everything, but there are no "." in that position and near positions as well.
However, after setting spring.jpa.show-sql=true I found out that there is indeed a "." symbol somewhere in that position, and the result SQL statement looks like this:
... and cast(array_length(.) as int4)>?
which means, that JPA can't put my 'arr' inside the array_length() function (is that so?)
Why does this happen? Am I doing something wrong?
Thank you in advance
My entity class looks like that:
#EqualsAndHashCode(callSuper = true)
#Entity
#Table
#Data
#NoArgsConstructor
#TypeDefs({
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
})
public class X extends BaseClass {
// private fields
#Type(type = "list-array")
#Column(name = "arr", columnDefinition = "bigint[]")
#ElementCollection
#OrderColumn
private List<Long> arr;
}
I tried without #ElementCollection and #OrderColumn annotations but that gives me cast errors

#ElementCollection and #OrderColumn are causing a first problem here. After they are removed (and the schema is setup correctly), the function call (SQL template) needs to be corrected.
The problem with #ElementCollection and #OrderColumn is that they represent an alternative approach for storing lists/arrays as part of an entity.
#ElementCollection stores the elements in a separate table, with each element in a separate row (each referencing the entity). To "remember" the correct order, an #OrderColumn is needed as part of the separate table, since rows are returned in arbitrary order if no order is specified (https://stackoverflow.com/a/20050403).
In contrast, ListArrayType and #Column(columnDefinition = "bigint[]") will enable saving the sequence of elements in one column of an entity row. Therefore, no separate table is used, and since the elements are not saved in separate rows, no additional order information is needed.
So without #ElementCollection and #OrderColumn the list mapping is already correctly setup. Be aware that your schema might currently be in a bad state, and you need to make sure that there is a bigint[] column in the entity table (can e.g. be auto-created by hibernate when #ElementCollection and #OrderColumn are removed).
2. Fixing the PostgresQL function call: array_length needs a second argument indicating the dimension of the array along which the length is returned (https://www.postgresql.org/docs/current/functions-array.html). So specifying the template string as follows should get you the correct result:
"function('array_length', {0}, 1)"
("1" being the requested array dimension).

Related

Simple Tagging Implementation with Spring Data JPA/Rest

I am trying to come up with a way of implementing tags for my entity that works well for me and need some help in the process. Let me write down some requirements I have in mind:
Firstly, I would like tags to show in entities as a list of strings like this:
{
"tags": ["foo", "bar"]
}
Secondly, I need to be able to retrieve a set of available tags across all entities so that users can easily choose from existing tags.
The 2nd requirement could be achieved by creating a Tag entity with the value of the Tag as the #Id. But that would make the tags property in my entity a relation that requires an extra GET operation to fetch. I could work with a getter method that resolves all the Tags and returns only a list of strings, but I see two disadvantages in that: 1. The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case. 2. The process of creating an entity requires to create all the Tags via a /tags endpoint first. That seem rather complicated for such a simple thing.
Also, I think I read somewhere that you shouldn't create a repository for an entity that isn't standalone. Would I create a Tag and only a Tag at any point in time? Nope.
I could store the tags as an #ElementCollection in my entity. In this case I don't know how to fulfill the 2nd requirement, though.
#ElementCollection
private Set<String> tags;
I made a simple test via EntityManager but it looks like I cannot query things that are not an #Entity in a result set.
#RestController
#RequestMapping("/tagList")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class TagListController implements RepresentationModelProcessor<RepositoryLinksResource> {
#PersistenceContext
private final #NonNull EntityManager entityManager;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<EntityModel<TagList>> get() {
System.out.println(entityManager.createQuery("SELECT t.tags FROM Training t").getFirstResult());
EntityModel<TagList> model = EntityModel.of(new TagList(Set.of("foo", "bar")));
model.add(linkTo(methodOn(TagListController.class).get()).withSelfRel());
return ResponseEntity.ok(model);
}
}
org.hibernate.QueryException: not an entity
Does anyone know a smart way?
The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case
This is precisely the issue with using entities as REST resource representations. They work fine until it turns out the internal representation (entity) does not match the external representation (the missing DTO).
However, it would probably make most sense performance-wise to simply use an #ElementCollection like you mentioned, because you then don't have the double join with a join table for the many-to-many association (you could also use a one-to-many association where the parent entity and the tag value are both part of the #Id to avoid a join table, but I'm not sure it's convenient to work with. Probably better to just put a UNIQUE(parent_id, TAG) constraint on the collection table, if you need it). Regarding the not an entity error, you would need to use a native query. Assuming that you have #ElementCollection #CollectionTable(name = "TAGS") #Column(name = "TAG") on tags, then SELECT DISTINCT(TAG) FROM TAGS should do the job.
(as a side note, the DISTINCT part of the query will surely introduce some performance penalty, but I would assume the result of that query is a good candidate for caching)

Is there a way to fetch an #Embedded field lazily?

I'm having some trouble trying to figure out how to set up a class that has an #Embedded field that must be fetched lazily. I tried to annotate the field with #Basic(fetch = FetchType.LAZY), but it causes the persistence API to treat the field as a basic type that implements Serializable, so it maps the field to a BYTEA field in the database (postgresql). I tested it on Derby too, and the same happens.
I also tried to annotate the fields of the #Embeddable class individually with #Basic(fetch = FetchType.LAZY) instead of annotating the #Embedded field of the entity that has it. The generated schema is correct in this case, but the fields are fetched eagerly when I load instances of the entity.
My understanding is that the #Basic annotation is used on basic fields/properties only, so the first case is expected. But why the fields of the #Embeddable class are fetched eagerly even if they are annotated with #Basic(fetch = FetchType.LAZY)? Also, I know that the fetch strategy can be specified by the #Basic and relationship annotations, but is there any other way to specify that fields should be fetched lazily? I'm using EclipseLink 2.6, but let me know if the behaviour is different for other versions of EclipseLink or for another provider.
Directly you cant, because of how #Embedded objects work, but by setting attributes in the object it should work.
#Basic(fetch=FetchType.LAZY)
Remember that lazy should be use only on collections or big objects, and that setting fetch type on lazy is only a clue for provider, it doesn't mean that it will always fetch it lazy rather than eager.

Map two fields to one database column

Question: Am I somehow able to map two fields of my Entity class to only one Column in the Database?
Scenario: The database is not fully normalized. There exists one Column which contains a composite information. It is not my actual use case, but an comprehensible example might be X- and Y-coordinate of a point in the plane. So the Database may contain a String 12:45 and the Entity class should contain only two integer field x width value 12 and ywith value 45.
Currently the Entity class has just two additional getter and setter for x and y and performs the proper translation. But I am wondering if there is a way to let JPA do this for me magically in the background.
I am already working with custom converter classes, e.g. for a proper mapping between between enums and database columns, but this works only for a "one-to-one" mapping between the field in the Entity class and the column in the database.
Of course it would be the most preferable way to redesign the table in the database, but that's not an option at the moment.
Vendor specific solutions are also fine.
2 Entity fields into one database column can be done fairly simply by specifying JPA use your accessor in the entity to handle the conversion:
#Entity
#Access(AccessType.FIELD)
class myEntity {
#Id
int id;
#Transient
String x;
#Transient
String y;
#Mutable //EclipseLink specific to prevent change tracking issues
#Access(AccessType.PROPERTY)
#Column(name="yourDatabaseFieldName")
private String getCoords() {
return x+":"+y;
}
private void setCoords(String coords) {
//parse the string and set x+y.
}
EclipseLink and Hibernate have transformation mappings that are able to handle the reverse; 2 or more database fields into one java property but this is outside of JPA.

Combine columns in entity framework into one column with the edmx designer

I'm using EntityFramework 5 EDMX designer and would like to combine the first & last name of a person into a single field value (name, for instance) on the entity.
I thought in previous versions there was a way to do this, but I don't see anything available to do what I need to do.
Is this still possible?
Unless I'm not understanding your question, I believe I've done that with a partial class that resembles something like the following:
public partial class person
{
public string name {
get
{
return firstname + " " + lastname;
}
set{ }
}
}
No it is not possible. You can create model defined function and use it in queries but it will still not be part of your entity. If your entity is read only you can create database view with combined column and map it instead of the table - it shows also main reason why combining columns into single property is not such easy task. Automatic concatenating during reading is easy but automatic decomposing to save correct value into correct column is hard and error prone.
If you need combined property for anything else than querying you can simply create another partial part of your entity class and add your own computed property. If you need the combined property for querying use the model defined function.
The way I do this is through a Computed Column as explained here:
How to make a computed column nullable in SQL Server
If you use a computed column you'll be able to use such a column in your LINQ queries. For example:
var users = Database.Users.Where(u => u.FullName.ToLower().Contains("string"));
You won't get errors like "not supported in LINQ to Entities" because this property is really a part of your model object. All the heavy lifting occurs on the database side.
Of course you could place a FullName property in a partial class and use it.
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
In this case, you'll have to call .ToList() first ( Database.Users.ToList(); ) to be able to use this property in LINQ queries. .ToList() will hydrate/bring all your Users to memory. This is not desirable!
You can also try the FullName property implementation that's described here: Calculated Columns in Entity Framework Code First Migrations

Envers generating "add"+"delete" edits instead of "modify" for Map<>

I have a field declared as a Map<MyEnum, String>, which is audited. When a change is made to one of the elements in the map, envers is generated two edits, an ADD and a DEL, rather that a single MOD, which in turn means a constraint violation when trying to insert into the audit table, since there are two edits for a single field in a single entity in the same revision.
I'm guessing I could probably work around the problem by making revision_type part of the table's key (which would permit one edit of each type per field per entity), but that seems like an ugly hack, besides the fact that I lose track of which happened first, and the fact that it's just wrong.
The field is being persisted properly, by the way... it's just the audit records that fail.
The field's declaration:
#ElementCollection
#CollectionTable(name = "configuration_property", joinColumns = #JoinColumn(name = "configuration_id"))
#MapKeyColumn(name = "property_name")
#Column(name = "property_value", columnDefinition = "longtext")
#MapKeyEnumerated(EnumType.STRING)
private Map<ConfigurationProperty, String> properties = new EnumMap<ConfigurationProperty, String>(ConfigurationProperty.class);
I'm using Hibernate v3.5.6.
I haven't been able to find any bug reports regarding this, so I'm thinking I'm doing something wrong... any ideas?
If you look at AbstractCollectionMapper.mapCollectionChanges, the current implementation only does additions and removals. I think the map is viewed as a collection of tuples: (key, value), so there are never any modifications. The bug is then in the schema generation, which should generate the keys properly. Please file a JIRA bug - https://hibernate.onjira.com, Envers component.