Suppose I have 2 JPA classes which model 2 entities in datastore (Google app engine) like these:
#Entity
public class Clazz {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key classKey;
#Basic
private String classId;
#Basic
private String className;
#ManyToOne
private Subject subject;
}
#Entity
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key subjectKey;
#Basic
private String subjectId;
#Basic
private String subjectName;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "subject")
private Set<Clazz> classes = new HashSet<Clazz>();
}
So, how to get Clazz objects which have classId and subjectId equal to given values using JPA criteria. I used this code but got an Exception like this:
em = EMF.get().createEntityManager();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Clazz> criteriaQuery = criteriaBuilder.createQuery(Clazz.class);
Root<Clazz> root = criteriaQuery.from(Clazz.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (searchObj.getClassId() != null && searchObj.getClassId().length() > 0) {
Expression<String> classIdExpression = root.get("classId");
predicates.add(criteriaBuilder.equal(classIdExpression, searchObj.getClassId()));
}
if (searchObj.getSubjectId() != null && searchObj.getSubjectId().length() > 0) {
Join<Clazz, Subject> join = root.join("subject");
predicates.add(criteriaBuilder.equal(join.get("subjectId"), searchObj.getSubjectId()));
}
if (predicates.isEmpty()) {
criteriaQuery.select(root);
} else {
criteriaQuery.select(root).where(predicates.toArray(new Predicate[predicates.size()]));
}
TypedQuery<Clazz> query = em.createQuery(criteriaQuery);
return query.getResultList();
Exception:
javax.persistence.PersistenceException: SELECT DN_THIS FROM thesis.filesharing.model.Clazz DN_THIS JOIN DN_THIS.subject WHERE (DN_THIS.classId = '44444') AND (DN_THIS.subject.subjectId = 'IT5834'): Can only reference properties of a sub-object if the sub-object is embedded.
at org.datanucleus.api.jpa.NucleusJPAHelper.getJPAExceptionForNucleusException(NucleusJPAHelper.java:302)
at org.datanucleus.api.jpa.JPAQuery.getResultList(JPAQuery.java:202)
at thesis.filesharing.dao.impl.ClassDAOImpl.countFoundClasses(ClassDAOImpl.java:203)
at thesis.filesharing.bo.impl.ClassBOImpl.countFoundClasses(ClassBOImpl.java:84)
at thesis.filesharing.test.TestController.searchClasses(TestController.java:143)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.google.appengine.tools.development.agent.runtime.Runtime.invoke(Runtime.java:115)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:746)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:687)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:915)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:811)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:617)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:796)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
Thanks in advance
Can only reference properties of a sub-object if the sub-object is
embedded.
is pretty explicit enough. GAE/Datastore can't cope easily with "joining". Whether using JPA Criteria of JPQL is not of relevance, since they equate to the same requirements on the datastore
Actually you should read some about GAE Datastore since datastore based on No-SQL database which is based mainly on data structure there aren't any relationships here (No Tables)
(Only Classes)
#OneToMany..etc not supported any more
From Wikipedia: In computing, NoSQL (commonly interpreted as "not only SQL"[1]) is a broad class of database management systems identified by non-adherence to the widely used relational database management system model. NoSQL databases are not built primarily on tables, and generally do not use SQL for data manipulation.
Since GAE doesn't play well with criteria joins, but accepts statements of the following type:
SELECT f FROM Foo f JOIN f.bar b WHERE b.id = "42"
I managed to implement a hacky solution of this issue using aliases, and it works perfectly. I cannot guarantee the safety of this method though, so use at your own risk.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> q = cb.createQuery(Foo.class);
Root<Foo> foo = q.from(Foo.class);
foo.join("bar").alias("b1"); // Comment 1
foo.alias("b1"); // Comment 2
q.select(foo).where(cb.equal(foo.get("id"), "42"));
foo.alias("f1"); // Comment 3
TypedQuery<Foo> query = em.createQuery(q);
List<Foo> foos = query.getResultList();
Few things to note:
Comment 1: This alias represents only the first b in the JPQL statement.
Comment 2: This alias represents the b in b.id.
Comment 3: This alias represents the f in the JPQL statement
The resulting statement in em.createQuery(q) is equivalent to the JPQL statement above.
Related
I try to use the Criteria API to create a dynamic JPA-Query. I need to find a key-value pair inside a map of the object.
The Object looks similar to the following one.
public class item {
private UUID id;
#Column(name = "properties", columnDefinition = "nvarchar")
private Map<String, Object> properties;
}
I thought I could use the MapJoin join or joinMap:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Item> criteriaQuery = cb.createQuery(Item.class);
Root<Item> itemRoot = criteriaQuery.from(Item.class);
criteriaQuery.select(itemRoot);
Join<String, Object> properties = itemRoot.join("properties");
// or
//MapJoin<Item, String, Object> properties = itemRoot.joinMap("properties");
Predicate pre1 = cb.equal(properties.get(ITEM_PROPERTY_1), "123");
Predicate pre2 = cb.equal(properties.get(ITEM_PROPERTY_2), "456");
Predicate propertiesPredicate = cb.and(pre1, pre2);
criteriaQuery.where(propertiesPredicate);
Item item = em.createQuery(criteriaQuery).getSingleResult();
But I've read that this is only for associations.
On the join i get an:
IllegalArgumentException: Requested attribute was not a map.
So could sb explain to me, how I will be able to find a key-value pair in a map with the Criteria API?
Edit: I am not able to change anything in the DB.
So I need to guess a little bit because you didn't show us your DB Table, that's why I answer a little bit more freely.
And as a disclaimer: it might be easier and it would be more efficient to query a real table instead of an serialized object/json.
But I would do it this way:
The Table in MSSQL:
create table ${schema}.myTable
(
id bigint identity
constraint PK_myStuff
primary key,
properties nvarchar(max) not null
) go
The Java entity (draft):
public class Item extends AbstractPersistable<...> {
#Column(name = "properties", columnDefinition = "nvarchar")
private String properties;
}
The Java Specification:
protected Specification<Item> customFilter(String filterArg) {
return (root, query, cb) ->
cb.like(root.get(Item_.properties), filterArg);
}
This way your query searches the properties for a string pattern.
Info:
https://vladmihalcea.com/sql-server-json-hibernate/
I am writing an application that will query a massive Database, that cannot be changed.
For that reason, my application does not need to map all Objects, since that would be useless and time consuming.
All entities there mapped are #Immutable.
I came across this relationship:
I want to Map Order, and have it reference Customer. It is, in fact, a Many to One Relationship, it just happens two be two Join clauses away.
I am not interested in neither R nor B, since they convey no information related to my requirement.
I envision something like this, but I know the syntax is invalid:
#Entity
#Immutable
#Table(name = "Order")
public class Order implements Serializable {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumns(value =
#JoinColumn(table = "R", name = "id", referencedColumnName = "R_id"),
#JoinColumn(table = "Customer", name = "id", referencedColumnName = "Customer_id")
)
private Customer customer;
... more data and getters/setters omitted ...
}
#Entity
#Immutable
#Table(name = "Customer")
public class Customer implements Serializable {
#Id
#Column(name = "id")
private Long id;
... more data and getters/setters omitted ...
}
Is there a way I can do this, without creating an entity for R?
EDIT: -------------------------
I tried the following, as per suggestion:
#ManyToOne
#JoinTable(name = "R",
joinColumns = #JoinColumn(name = "id", referencedColumnName = "R_id"),
inverseJoinColumns = #JoinColumn(name = "id", referencedColumnName = "Customer_id"))
private Customer customer;
However, I get the following error:
Unable to find column with logical name: R_id in org.hibernate.mapping.Table(Order) and its related supertables and secondary tables
You could use the #JoinTable annotation for the following schema.
in this way
#Entity
#Table(name = "Order")
public class Order {
// ...
#ManyToOne
#JoinTable(
name = "R",
joinColumns = #JoinColumn(name = "ord_id"),
inverseJoinColumns = #JoinColumn(name = "customer_id"))
private Customer customer;
// ...
}
But for your case it looks like not possible to avoid usage of entity for intermediate table R due to the lack of foreign key to the Order table in the R.
I already created question before which was complicated. So I am creating a new one which will be as simple as possible.
I am having a problem with Spring JPA especially twice nested structure.
Here is a super simple relationship diagram:
The country has multiple rivers and rivers can flow through multiple countries. The river has multiple bridges but the bridge can be built over 1 river only. So M:N -> 1:N
This is the expected result:
{
countries: [
{
countryID: 1,
rivers: [
{
riverID: 1,
bridges: [{
bridgeID: 1
}]
}
]
}
]
}
And this is what I have.
Country entity
public class Country {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "country_river",
joinColumns = { #JoinColumn(name = "countryid") },
inverseJoinColumns = { #JoinColumn(name = "riverid") })
private Set<River> rivers = new HashSet<>();
River entity
public class River {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JoinColumn(name = "riverid")
private Set<Bridge> Bridges;
Don't need to mention Bridge entity since there is only ID column specified ( I am using unidirectional relation)
The problem is, that bridges are not filtered based on country. As you can see in the expected output, I would like to list all countries and it's associated rivers and bridges on them in the given country.
Do I have to create a custom query on Bridges inside River entity (If so, how.)? Or do I have to somehow change the structure of the database?
My application is using SQLServer and JPA2 in the backend. App makes use of a timestamp column (in the SQLServer sense, which is equivalent to row version see here) per entity to keep track of freshly modified entities. NB SQLServer stores this column as binary(8).
Each entity has a respective timestamp property, mapped as #Lob, which is the way to go for binary columns:
#Lob
#Column(columnDefinition="timestamp", insertable=false, updatable=false)
public byte[] getTimestamp() {
...
The server sends incremental updates to mobile clients along with the latest database timestamp. The mobile client will then pass the old timestamp back to the server on the next refresh request so that the server knows to return only fresh data. Here's what a typical query (in JPQL) looks like:
select v from Visit v where v.timestamp > :oldTimestamp
Please note that I'm using a byte array as a query parameter and it works fine when implemented in JPQL this way.
My problems begin when trying to do the same using the Criteria API:
private void getFreshVisits(byte[] oldVersion) {
EntityManager em = getEntityManager();
CriteriaQuery<Visit> cq = cb.createQuery(Visit.class);
Root<Visit> root = cq.from(Visit.class);
Predicate tsPred = cb.gt(root.get("timestamp").as(byte[].class), oldVersion); // compiler error
cq.where(tsPred);
...
}
The above will result in compiler error as it requires that the gt method used strictly with Number. One could instead use the greaterThan method which simply requires the params to be Comparable and that would result in yet another compiler error.
So to sum it up, my question is: how can I use the criteria api to add a greaterThan predicate for a byte[] property? Any help will be greatly appreciated.
PS. As to why I'm not using a regular DateTime last_modified column: because of concurrency and the way synchronization is implemented, this approach could result in lost updates. Microsoft's Sync Framework documentation recommends the former approach as well.
I know this was asked a couple of years back but just in case anyone else stumbles upon this.. In order to use a SQLServer rowver column within JPA you need to do a couple of things..
Create a type that will wrap the rowver/timestamp:
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.xml.bind.annotation.XmlTransient;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
/**
* A RowVersion object
*/
public class RowVersion implements Serializable, Comparable<RowVersion> {
#XmlTransient
#JsonIgnore
private byte[] rowver;
public RowVersion() {
}
public RowVersion(byte[] internal) {
this.rowver = internal;
}
#XmlTransient
#JsonIgnore
public byte[] getRowver() {
return rowver;
}
public void setRowver(byte[] rowver) {
this.rowver = rowver;
}
#Override
public int compareTo(RowVersion o) {
return new BigInteger(1, rowver).compareTo(new BigInteger(1, o.getRowver()));
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RowVersion that = (RowVersion) o;
return Arrays.equals(rowver, that.rowver);
}
#Override
public int hashCode() {
return Arrays.hashCode(rowver);
}
}
The key here is that it implement Comparable if you want to use it in calculations (which you definitely do)..
Next create a AttributeConverter that will move from a byte[] to the class you just made:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
* JPA converter for the RowVersion type
*/
#Converter
public class RowVersionTypeConverter implements AttributeConverter<RowVersion, byte[]> {
#Override
public byte[] convertToDatabaseColumn(RowVersion attribute) {
return attribute != null ? attribute.getRowver() : null;
}
#Override
public RowVersion convertToEntityAttribute(byte[] dbData) {
return new RowVersion(dbData);
}
}
Now let's apply this RowVersion attribute/type to a real world scenario. Let's say you wanted to find all Programs that have changed on or before some point in time.
One straightforward way to solve this would be to use a DateTime field in the object and timestamp column within db. Then you would use 'where lastUpdatedDate <= :date'.
Suppose that you don't have that timestamp column or there's no guarantee that it will be updated properly when changes are made; or let's say your shop loves SQLServer and wants to use rowver instead.
What to do? There are two issues to solve.. one how to generate a rowver and two is how to use the generated rowver to find Programs.
Since the database generates the rowver, you can either ask the db for the 'current max rowver' (a custom sql server thing) or you can simply save an object that has a RowVersion attribute and then use that object's generated RowVersion as the boundary for the query to find the Programs changed after that time. The latter solution is more portable is what the solution is below.
The SyncPoint class snippet below is the object that is used as a 'point in time' kind of deal. So once a SyncPoint is saved, the RowVersion attached to it is the db version at the time it was saved.
Here is the SyncPoint snippet. Notice the annotation to specify the custom converter (don't forget to make the column insertable = false, updateable = false):
/**
* A sample super class that uses RowVersion
*/
#MappedSuperclass
public abstract class SyncPoint {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "current_database_version", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "created_date_utc", columnDefinition = "timestamp", nullable = false)
private DateTime createdDate;
...
Also (for this example) here is the Program object we want to find:
#Entity
#Table(name = "program_table")
public class Program {
#Id
private Integer id;
private boolean active;
// type is rowver for SQLServer, blob(8) for postgresql and h2
#Column(name = "rowver", insertable = false, updatable = false)
#Convert(converter = RowVersionTypeConverter.class)
private RowVersion currentDatabaseVersion;
#Column(name = "last_chng_dt")
private DateTime lastUpdatedDate;
...
Now you can use these fields within your JPA criteria queries just like anything else.. here is a snippet that we used inside a spring-data Specifications class:
/**
* Find Programs changed after a synchronization point
*
* #param filter that has the changedAfter sync point
* #return a specification or null
*/
public Specification<Program> changedBeforeOrEqualTo(final ProgramSearchFilter filter) {
return new Specification<Program>() {
#Override
public Predicate toPredicate(Root<Program> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
if (filter != null && filter.changedAfter() != null) {
// load the SyncPoint from the db to get the rowver column populated
SyncPoint fromDb = synchronizationPersistence.reload(filter.changedBeforeOrEqualTo());
if (fromDb != null) {
// real sync point made by database
if (fromDb.getCurrentDatabaseVersion() != null) {
// use binary version
return cb.lessThanOrEqualTo(root.get(Program_.currentDatabaseVersion),
fromDb.getCurrentDatabaseVersion());
} else if (fromDb.getCreatedDate() != null) {
// use timestamp instead of binary version cause db doesn't make one
return cb.lessThanOrEqualTo(root.get(Program_.lastUpdatedDate),
fromDb.getCreatedDate());
}
}
}
return null;
}
};
}
The specification above works with both the binary current database version or a timestamp.. this way I could test my stuff and all the upstream code on a database other than SQLServer.
That's it really: a) type to wrap the byte[] b) JPA converter c) use attribute in query.
I'm using JPA on GAE and this query return a List containing 1 element.
This element is a org.datanucleus.store.types.sco.backed.ArrayList (and it's finally containing my results) while I'm expecting a List of Products. What I'm doing wrong?
Thanx in advance!
Query query = entityManager.createQuery
("select p.products from Place p where p.id = :Id" );
query.setParameter("Id",id);
List<Product> resultList = query.getResultList();
//for debugging purpose
assert (resultList.get(0) instanceof Product);
if (resultList.size() > 0)
{
//raise a cast exception here
Product p = resultList.get(0);
}
#Entity
public class Place {
private Collection<Product> products;
#OneToMany(cascade = CascadeType.ALL)
public Collection<Product> getProducts() {
return products;
}
public void setProducts(Collection<Product> products) {
this.products = products;
}
private String id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Selecting a multi-valued field from a JPQL query is illegal when the JPQL spec says the select item should be
"single_valued_path_expression | scalar_expression | aggregate_expression |
identification_variable | OBJECT(identification_variable) | constructor_expression"
so answers saying this query is correct are wrong. If you want the "products" for a Place then you retrieve the Place and it has the products.
I am not much familiar with DataNucleus, so haven't executed. But with JPA, the query should work fine. You can try the below code to build query, which returns results according to the specified class.
entityManager.createQuery("SELECT p.products FROM Place p WHERE p.id = :Id", Product.class);
From Documentation :
<T> TypedQuery<T> createQuery(java.lang.String qlString,
java.lang.Class<T> resultClass)
Create an instance of TypedQuery for executing a Java Persistence
query language statement. The select list of the query must contain
only a single item, which must be assignable to the type specified by
the resultClass argument.
Parameters:
qlString - a Java Persistence query string
resultClass - the type of the query result
Edit :
You can try the following code.
Place place = entityManager.createQuery("SELECT p FROM Place p WHERE p.id = :Id", Place.class).getSingleResult();
List<Products> products = place.getProducts();
Also as a side-note, you are using JPA, but #Extension seems to be JDO specific annotation.
That class javadoc tells that it implements java.util.List, so it is a valid return type.
Remember that the specification says that you get a List as a return type, not a java.util.ArrayList, so any class that implements List is as valid as any other.
UPDATE:
Try:
SELECT pr FROM Place pl INNER JOIN pl.products pr WHERE pl.id = :Id