Prevent unwanted cascaded table updates - jpa

I am creating my first JPA based project. My application features several tables with foreign key relationships for integrity purposes. Many related tables are normalized lookup tables.
Consider the tables Person and Account, having a fk relationship on accountid:
+-Person----+
| personid | +-Account---+
| accountid*|==============>| accountid |
| ... | | ... |
+-----------+ +-----------+
In the front end (JSF), I created a form for new Person objects containing a selectOneMenu list with all accounts, and the selected accountid is added to the new person which is inserted correctly into the database.
However, the Account table is also updated automatically and I cannot find a way to stop this from happening.
My classes are annotated as follows:
#Entity
#Table(name="usr_person")
#NamedQuery(name="Person.findAll", query="SELECT u FROM Person u")
public class Person implements Serializable
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="person_id")
private int personId;
//bi-directional one-to-one association to UsrAccount
#OneToOne
#JoinColumn(name="account_id",insertable=true,updatable=true)
private Account usrAccount;
...
#Entity
#Table(name="usr_account")
#NamedQuery(name="UsrAccount.findAll", query="SELECT u FROM Account u")
public class Account implements Serializable
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="account_id")
private int accountId;
//bi-directional one-to-one association to UsrPerson
#OneToOne(mappedBy="usrAccount")
private Person usrPerson;
...
The front end calls the below method which simply uses the merge() function of EntityManager to persist the new Person object:
public String savePerson()
{
try
{
em.getTransaction().begin();
person = em.merge( person );
em.getTransaction().commit();
}
catch (Exception e)
{
...
This leads to an UPDATE call for every object in the Account table.
UPDATE
I removed the EntityManager.merge() call from my code and the UPDATE statements are still there. Somehow the JSF form leads to automatic updates of the Account objects backing it. The lines below are from the JPA trace:
17448 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 1172878998 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) mvreijn, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 90, (int) 1]
17449 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
17449 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 386904456 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) afolmer, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 90, (int) 2]
17450 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
17450 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> executing prepstmnt 1322606395 UPDATE usr_account SET loginname = ?, password = ?, privileges = ? WHERE account_id = ? [params=(String) annuska, (String) ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4, (int) 80, (int) 3]
17451 <snip> openjpa.jdbc.SQL - <t 1905431636, conn 233456683> [1 ms] spent
These statements are executed when I commit() the transaction. For some reason the query and subsequent transaction leads JPA to believe all Account objects from the selectOneMenu are modified; how do I prevent this? Detach them first one-by-one?
UPDATE 2
My persistence.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="matco">
<class>matco.model.Changelog</class>
<class>matco.model.Item</class>
<class>matco.model.ItemAssignment</class>
<class>matco.model.ItemMaintenance</class>
<class>matco.model.LstType</class>
<class>matco.model.LstColor</class>
<class>matco.model.LstCondition</class>
<class>matco.model.LstSize</class>
<class>matco.model.Team</class>
<class>matco.model.Account</class>
<class>matco.model.Person</class>
<class>matco.model.CatBrand</class>
<class>matco.model.CatItem</class>
<class>matco.model.CatPrice</class>
<class>matco.model.CatSupplier</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/matco" />
<property name="javax.persistence.jdbc.user" value="****" />
<property name="javax.persistence.jdbc.password" value="****" />
<!-- TODO remove this in favor of enhancement -->
<property name="openjpa.RuntimeUnenhancedClasses" value="supported"/>
<property name="openjpa.Log" value="DefaultLevel=WARN, Runtime=INFO, Tool=INFO, SCHEMA=TRACE, SQL=TRACE"/>
<property name="openjpa.ConnectionFactoryProperties" value="PrintParameters=true" />
</properties>
</persistence-unit>
</persistence>
Meanwhile, I have detached the Account objects after I query them in the backing bean, like this:
public List<Account> getUsrAccounts()
{
List<Account> accts = em.createNamedQuery( "Account.findAll", Account.class ).getResultList();
for (Account acct : accts)
em.detach(acct);
return accts;
}
Then when I commit the new Person object, only the Account that is linked to the new Person is updated by JPA. Even that is undesirable, and I get the feeling that I am misusing JPA in some way.
What is the normal way to use a non-editable lookup table with JPA?

Do not have <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> enabled in your persistence.xml. Figure out another enhancement strategy.

Try adding to your #OneToOne mappings cascade=CascadeType.REFRESH)
This will indicate to your ORM framework to cascade only on refreshes (while reading entity) and to not cascade on updates when trying to save only one of these entities.

Related

Hibernate Search query searches all tables instead of only the specified class' table

I have an abstract class Product that are subclassed by ProductA, ProductB and ProductC.
And the classes ProductA, ProductB and ProductC are mapped to the database tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C respectively.
Now I want to perform a full-text search on ProductA entities by Hibernate Search.
I wrote some code to successfully get expected ProductA entities from database, but I found in the log (as follows) that the executed Hibernate Search query actually searched all the tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C instead of only the table PRODUCTS_A I expected.
I want to get only ProductA entities, why are ProductB and PRODUCTS_C tables also searched? Is there a way to fix this?
Log
You can see from the following working log outputted by Hibernate that besides the PRODUCTS_A table, the PRODUCTS_B and PRODUCTS_C tables are also searched.
Hibernate: select this_.ID as ID1_2_0_, this_.NAME as NAME2_2_0_, this_.FEATURE as FEATURE3_2_0_, this_.CREATED_DATE as CREATED_4_2_0_, this_.MODIFIED_DATE as MODIFIED5_2_0_, this_.FEATURE_A1 as FEATURE_1_3_0_, this_.FEATURE_A2 as FEATURE_2_3_0_, this_.FEATURE_B1 as FEATURE_1_4_0_, this_.FEATURE_B2 as FEATURE_2_4_0_, this_.FEATURE_C1 as FEATURE_1_5_0_, this_.FEATURE_C2 as FEATURE_2_5_0_, this_.clazz_ as clazz_0_ from ( select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, FEATURE_A1, FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 1 as clazz_ from PRODUCTS_A union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, FEATURE_B1, FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 2 as clazz_ from PRODUCTS_B union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, FEATURE_C1, FEATURE_C2, 3 as clazz_ from PRODUCTS_C ) this_ where (this_.ID in (?))
Code
Entity Classes
Here are the entity classes Product, ProductA, ProductB and ProductC.
public abstract class Product {
#Id
protected Long id;
#Field
protected String name;
#Field
protected String feature;
protected Date createdDate;
protected Date modifiedDate;
// Getters and setters...
}
#Entity
#Indexed
public class ProductA extends Product {
#Field
private String featureA1;
#Field
private String featureA2;
public ProductA() {
}
// Getters and setters...
}
The ProductB and ProductC classes are similar as the ProductA class.
Hibernate Mapping File
Product.hbm.xml
The union-subclass element is used to reflect the subclass relationship between the Product class and the ProductA, ProductB and ProductC classes.
<hibernate-mapping package="com.raychen518.study.hibernate">
<class name="Product" abstract="true">
<id name="id" column="ID">
<generator class="increment" />
</id>
<property name="name" column="NAME" />
<property name="feature" column="FEATURE" />
<property name="createdDate" type="timestamp" column="CREATED_DATE" />
<property name="modifiedDate" type="timestamp" column="MODIFIED_DATE" />
<union-subclass name="ProductA" table="PRODUCTS_A">
<property name="featureA1" column="FEATURE_A1" />
<property name="featureA2" column="FEATURE_A2" />
</union-subclass>
<union-subclass name="ProductB" table="PRODUCTS_B">
<property name="featureB1" column="FEATURE_B1" />
<property name="featureB2" column="FEATURE_B2" />
</union-subclass>
<union-subclass name="ProductC" table="PRODUCTS_C">
<property name="featureC1" column="FEATURE_C1" />
<property name="featureC2" column="FEATURE_C2" />
</union-subclass>
</class>
</hibernate-mapping>
Hibernate Configuration File
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">org.postgresql.Driver</property>
<property name="connection.url">jdbc:postgresql://localhost:5432/test</property>
<property name="connection.username">postgres</property>
<property name="connection.password">admin</property>
<property name="connection.pool_size">1</property>
<property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
<property name="show_sql">true</property>
<property name="hbm2ddl.auto">validate</property>
<!-- Setting for Hibernate Search -->
<property name="hibernate.search.lucene_version">LUCENE_CURRENT</property>
<property name="hibernate.search.default.directory_provider">filesystem</property>
<property name="hibernate.search.default.indexBase">hibernate.search.test/lucene/indexes</property>
<mapping resource="Product.hbm.xml" />
</session-factory>
</hibernate-configuration>
Application Launcher Class
The ProductManager class contains the main method, thus serves as the application launcher. It starts the Hibernate Search indexing process, clears the PRODUCTS_A, PRODUCTS_B and PRODUCTS_C tables and inserts some sample product data into them, and finally performs a full-text search using the Hibernate Search.
What confuses me is that I have specified the target entity as ProductA.class in the following statement Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class); in the method searchProducts(). Why does Hibernate Search also search ProductB and ProductC entities?
public class ProductManager {
public static void main(String[] args) throws InterruptedException {
ProductManager productManager = new ProductManager();
productManager.indexAllProducts();
productManager.deleteAllProducts();
productManager.generateSomeProducts();
productManager.searchProducts();
}
private void indexAllProducts() throws InterruptedException {
FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
fullTextSession.createIndexer().startAndWait();
}
public void deleteAllProducts() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
#SuppressWarnings("unchecked")
List<Product> results = session.createQuery("from Product").list();
for (Product result : results) {
session.delete(result);
}
session.getTransaction().commit();
}
public void generateSomeProducts() {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
session.save(new ProductA("feature001", "featureA1001", "featureA2001", new Date()));
session.save(new ProductA("feature002", "featureA1002", "featureA2002", new Date()));
session.save(new ProductA("feature003", "featureA1003", "featureA2003", new Date()));
session.save(new ProductB("feature001", "featureB1001", "featureB2001", new Date()));
session.save(new ProductB("feature002", "featureB1002", "featureB2002", new Date()));
session.save(new ProductB("feature003", "featureB1003", "featureB2003", new Date()));
session.save(new ProductC("feature001", "featureC1001", "featureC2001", new Date()));
session.save(new ProductC("feature002", "featureC1002", "featureC2002", new Date()));
session.save(new ProductC("feature003", "featureC1003", "featureC2003", new Date()));
session.getTransaction().commit();
}
private void searchProducts() {
FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
fullTextSession.beginTransaction();
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(ProductA.class).get();
org.apache.lucene.search.Query luceneQuery = queryBuilder.keyword().onFields("feature").matching("feature002").createQuery();
// Set the 2nd method parameter using "Product.class" to get products of the types ProductA, ProductB and ProductC.
// Set the 2nd method parameter using "ProductA.class" to get products of the types ProductA.
Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class);
#SuppressWarnings("unchecked")
List<Product> queryResults = query.list();
for (Product queryResult : queryResults) {
System.out.println("queryResult: " + queryResult);
}
fullTextSession.getTransaction().commit();
}
}
I fixed it recently as https://hibernate.atlassian.net/browse/HSEARCH-2301 following another Stackoverflow question.
It hasn't been released yet but the patch is rather small and localized in only one file: https://github.com/hibernate/hibernate-search/pull/1122/files so you should be able to apply it locally on the 5.5 branch.
Use https://patch-diff.githubusercontent.com/raw/hibernate/hibernate-search/pull/1122.diff to get the raw diff file.
UPDATE we fixed it in 5.5.4.Final: http://in.relation.to/2016/06/29/Polishing-Polishing-And-More-Polishing-Hibernate-Search-5-5-4-Final/

Removing entity violates not null constraint on field

I have this entity
#Entity
class PieceAvailable {
...
#JoinColumn(name = "id_product", referencedColumnName = "id_product")
#ManyToOne(optional = false)
private Product idProduct;
...
}
I also have Product entity however it's code is pretty much irrelevant (there are no relation from Product back to PieceAvailable).
And there is also a code
PieceAvailable pa = entityManager.find(PieceAvailable.class, 52);
entityManager.remove(pa);
persistence.xml (testPU persistence-unit is probably not used right now)
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="crm-ejbPU" transaction-type="JTA">
<jta-data-source>jdbc/crm</jta-data-source>
<class>my.company.crm.server.entity.CrmSetting</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
</properties>
</persistence-unit>
<persistence-unit name="testPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>my.company.crm.server.entity.CrmSetting</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
</persistence-unit>
</persistence>
which throws exception:
Caused by: javax.persistence.PersistenceException: Exception
[EclipseLink-4002] (Eclipse Persistence Services -
2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException Internal
Exception: org.postgresql.util.PSQLException: ERROR: null value in
column "id_product" violates not-null constraint Detail: Failing row
contains (null)(52, null, 2015-08-12 16:05:41.166, 1,
205.33000000000001, null, null, null, null, null, 2015-08-12 16:05:41.166, 0, null). Error Code: 0 Call: UPDATE piece_available SET
id_invoice_position_pz = ?, id_piece_available_original = ?,
id_product = ?, id_store = ? WHERE (id_piece_available = ?) bind =>
[5 parameters bound]
My goal is to remove entity as clearly stated. I don't know why eclipselink firstly fill relations with null. But it's happening. This violates not null constraint which obviously is setted up in my PostgreSQL. In JPA also is clearly stated that for this mapping optional = false. So it's required.
How can I avoid this exception? I wouldn't like to delete this not null constraint.
When I was preparing minimal example project for this topic I found out that my PieceAvailable entity is pretty uncommon.
#Entity
#Table(name = "piece_available")
public class PieceAvailable implements Serializable {
...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_piece_available")
private Integer idPieceAvailable;
#JoinColumn(name = "id_product", referencedColumnName = "id_product")
#ManyToOne(optional = false)
private Product idProduct;
#JoinColumn(name = "id_piece_available_original", referencedColumnName = "id_piece_available")
#ManyToOne(optional = true)
private PieceAvailable idPieceAvailableOriginal;
...
}
PieceAvailable entity maps relation to itself (field idPieceAvailableOriginal) and in some cases in my application it seems that entity maps to itself. In these cases UPDATE statement will occur before removal. So when I persist entity like:
PieceAvailable pa = new PieceAvailable();
...
pa.setIdPieceAvailableOriginal(pa);
Then after invocation remove method with entity like this above as argument I will get my exception.
I will have to fix this strange relation. However for now I found that as workaround I can set null in idPieceAvailableOriginal before removal and I won't get any exception.
PieceAvailable pa = entityManager.find(PieceAvailable.class, 52);
pa.setIdPieceAvailableOriginal(null);
entityManager.remove(pa);

My entities are not persisting showing javax.persistence.TransactionRequiredException

I have recently migrated to JPA 2.0 (EclipseLink 2.4.2 Juno) in TomEE 1.7 in Eclipse IDE, As i am storing the values they are getting stored and retrieved fine, but they are not persisting into the database when is use entitymanager.flush()it is showing javax.persistence.TransactionRequiredException Here is my code
Create.java (register method)
public static int register(String first, String last, String email,
String date, String phone, String address, String pin, Login login) {
try {
System.out.println("registering persisting the entity");
EntityManagerFactory emf = Persistence
.createEntityManagerFactory("FirstEE");
EntityManager manager = emf.createEntityManager();
manager.getTransaction().begin();
//
// Query query = manager
// .createQuery("select l from Truck l");
Login log = login;
System.out.println(log.getUsername() + "username"
+ log.getPassword() + "password");
User reg = new User();
reg.setLogin(log);
reg.setDate(date);
reg.setEmail(email);
reg.setFirst(first);
reg.setLast(last);
reg.setPhone(phone);
reg.setAddress(address);
reg.setPin(pin);
manager.persist(reg);
manager.getTransaction().commit();
manager.flush();
manager.close();
emf.close();
// FacesContext.getCurrentInstance().addMessage("reg:result",
// new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error Message",
// "Registered Successfully"));
FacesContext facesContext = FacesContext.getCurrentInstance();
FacesMessage facesMessage = new FacesMessage(
"Registered Successfully");
facesContext.addMessage(null, facesMessage);
System.out.println("after message global");
return 1;
} catch (Exception e) {
System.out.println("hai this is exception caught:" + e);
System.out.println("hai" + e.getMessage());
FacesContext.getCurrentInstance().addMessage(
"reg:result",
new FacesMessage("Something went wrong",
"\tSomething went wrong\t"));
// FacesContext facesContext = FacesContext.getCurrentInstance();
// FacesMessage facesMessage = new
// FacesMessage("Something went wrong");
// facesContext.addMessage(null, facesMessage);
}
return 0;
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="FirstEE" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<non-jta-data-source>FirstEE</non-jta-data-source>
<!-- <exclude-unlisted-classes>false</exclude-unlisted-classes> -->
<class>com.jason.Entity.User</class>
<class>com.jason.ManagedBean.Login</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/yash" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="root" />
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="eclipselink.logging.level" value="FINEST" />
<property name="eclipselink.ddl-generation" value="create-tables" />
</properties>
</persistence-unit>
</persistence>
I cant figure out the problem data is getting retreived and stored but it is not updating and not getting persisted in database
The commit()method is committing your transaction immediately. Therefore, the changes are writting into the database & your previously open transaction also ends in this moment. When you call flush() afterwards, there is no open transaction so the flush() operation - complains as you experience it - with a javax.persistence.TransactionRequiredException. See also javax.persistence
Interface EntityManager#flush()
void flush()
Synchronize the persistence context to the underlying database.
Throws:
TransactionRequiredException - if there is no transaction
You should call the commit() method after you synced the state of your EntityManager, like so
manager.getTransaction.begin();
// ...
// do something with your entities in between
// ...
manager.persist(reg);
manager.flush(); // in your case: could also be skipped
// finally - if nothing fails - we are safe to commit
manager.getTransaction().commit();
Another hint:
You should avoid to mix your UI-side code (JSF...) with Backend code. This is generally considered 'spaghetti' anti-pattern here.
Moreover, do not open and close EntityManagerFactory every time (!) you call this UI-bound method (register(...)), as this is a performance killer - at least it produces unnecessary processing every time somebody tries to register here. At least, you should create an instance of EntityManagerFactory (or: EntityManager) as a field (via a DBService class) and reuse this to communicate with the application backend.
The error indicates you cant call flush outside of a transaction. Your code clearly shows you are calling flush right after calling manager.getTransaction().commit(). There is no need to call flush after commit, since the entityManager synchronizes to the database on commit or flush. You only use flush if you have a long running transaction and you want to synchronize at particular stages.
For the rest, turn on EclipseLink logging https://wiki.eclipse.org/EclipseLink/Examples/JPA/Logging and it will show you want it is doing on the commit/flush calls.

EclipseLink Mongodb CriteriaQuery on #Embedded field

I am trying to use CriteriaQuery with Eclipselink 2.5.2 and MongoDb. I have the following classes:
User:
#Entity
#NoSql(dataFormat=DataFormatType.MAPPED)
public class User implements Serializable {
private static final long serialVersionUID = -1;
#Id
#GeneratedValue
#Field(name="_id")
private String id;
#Field(name="USERID")
private String userId;
#Field(name="fullName")
private String fullName;
#Field(name="AGE")
private String age;
#Field(name="STATUS")
private String status;
#Embedded
#Field(name="address")
private Address address = new Address();
...Getters/Setters
}
Address:
#Embeddable
#NoSql(dataFormat=DataFormatType.MAPPED)
public class Address {
#Field(name="addressId")
private String addressId = "";
#Field(name="#addressValue")
private String addressValue = "";
...Getters/Setters
}
When I run the following CriteriaQuery:
// Criteria
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
// From
Root<User> from = query.from(User.class);
// Filter
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.equal(from.get("address").get("addressValue"), "1234 Main St"));
Predicate[] preds = null;
preds = predicates.toArray(new Predicate[predicates.size()]);
query.where(preds);
I get the following error:
Criteria expression is of primitive type and can not be further
navigated.
However if u run a JPQL query it works just fine.
em.createQuery("Select u from User u where u.address.addressValue = :address")
.setParameter("address", "1234 Main St")
.setMaxResults(1).getSingleResult();
Is there something special I need to do in order to query on fields in the Embedded object? If I just filter on User things are find in CriteriaQuery.
Thanks
NOTE: the below answer it's not a solution, rather a detailed explanation why it's not working currently.
I have reproduced the problem with EclipseLink 2.5.2 and MongoDB. A few conclusions:
looks like an embedded field mapped to NoSQL data is not supported for MongoDB
in order to get a satisfactory solution it may be necessary to create a bug report
I am not sure how it behaves with other NoSQL databases since I had no opportunity to cross-check any of them
the problem may be related to all NoSQL adapters leveraging EISCompositeObjectMapping class (more on this below)
As a temporary workaround consider replacing Address embeddable by placing its field directly in the User entity.
Here's what I have found - setting up eclipselink.logging.metamodel level to FINEST in persistence.xml
<properties>
<property name="eclipselink.target-database" value="org.eclipse.persistence.nosql.adapters.mongo.MongoPlatform"/>
<property name="eclipselink.nosql.connection-spec" value="org.eclipse.persistence.nosql.adapters.mongo.MongoConnectionSpec"/>
<property name="eclipselink.nosql.property.mongo.port" value="27017"/>
<property name="eclipselink.nosql.property.mongo.host" value="127.0.0.1"/>
<property name="eclipselink.nosql.property.mongo.db" value="odm_eclipselink"/>
<property name="eclipselink.logging.level" value="FINEST"/>
<property name="eclipselink.logging.metamodel" value="FINEST"/>
</properties>
shows the following output for EclipseLink + MongoDB:
[EL Finest]: metamodel: 2015-05-12 20:33:48.906--Thread(Thread[main,5,main])--Metamodel processing: The mapping type [org.eclipse.persistence.eis.mappings.EISCompositeObjectMapping[address]] in the attribute [SingularAttributeImpl[null,org.eclipse.persistence.eis.mappings.EISCompositeObjectMapping[address]]] is currently unsupported.
[EL Finest]: metamodel: 2015-05-12 20:33:48.906--Thread(Thread[main,5,main])--Metamodel processing: The class type is null for the attribute: SingularAttributeImpl[null,org.eclipse.persistence.eis.mappings.EISCompositeObjectMapping[address]].
[EL Finer]: metamodel: 2015-05-12 20:33:48.906--ServerSession(13500464)--Thread(Thread[main,5,main])--Canonical Metamodel class [eclipselink.User_] found and instantiated during initialization.
[EL Finer]: metamodel: 2015-05-12 20:33:48.906--ServerSession(13500464)--Thread(Thread[main,5,main])--Canonical Metamodel class [eclipselink.Address_] found and instantiated during initialization.
The above output clearly states that the feature is currently unsupported (well, at least from end-user's point of view).
From the static metamodel point of view User.Address embedded attribute is represented by SingularAttribute<User, Address>. Going deeper we can figure out that org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl instantiates SingularAttributeImpl<EntityTypeImpl<User>, BasicTypeImpl<Object>>
with help of EISCompositeObjectMapping class which defines how an attribute (address=embeddable) is aggregated and structured in relation to its parent (user=enity).
Now, the root-cause of the problem is the most likely lack of a proper mapping as shown on the org.eclipse.persistence.mappings class hierarchy below:
CoreMapping
|
DatabaseMapping
|
AggregateMapping
|
AbstractCompositeObjectMapping | #Override
| | public Class getAttributeClassification() {
EISCompositeObjectMapping ------| return null; // root-cause of the problem!
| }
|
Lack of implementation in getAttributeClassification() method leads to unsupported mapping in metamodel log and instantiation of BasicTypeImpl (rather than EmbeddableTypeImpl) which is wrong in this particular case.
For comparison, the same scenario for EclipseLink + PostgreSQL works like a charm:
[EL Finer]: metamodel: Canonical Metamodel class [com.wypieprz.jpa.example.model.Address_] found and instantiated during initialization.
[EL Finer]: metamodel: Canonical Metamodel class [com.wypieprz.jpa.example.model.User_] found and instantiated during initialization.
The important difference is under the hood as org.eclipse.persistence.jpa.PersistenceProvider which is related to SQL databases uses different mapping class in comparison
with MongoDB case. This time the metamodel implementation instantiates SingularAttributeImpl<EntityTypeImpl<User>, EmbeddableTypeImpl<Object>>
with help of AggregateObjectMapping class whose implementation works as expected:
CoreMapping
|
DatabaseMapping
|
AggregateMappping | #Override
| | public Class getAttributeClassification() {
AggregateObjectMapping ------| return getReferenceClass();
| }
|
This time getAttributeClassification() method leads to found and instantiated in metamodel log and instantiation of EmbeddableTypeImpl which is expected.

Transaction aware objects in stateless EJB

I'm little confused how transactions work in EJBs. I've always thought that all transaction aware objects in container managed EJBs are all committed or rollbacked when a method with TransactionAttribute=REQUIRED_NEW is finished but unfortunately it's not in my case. I don't have my code in front of me so I can't include whole example but what I ask for is just the confirmation of the idea of how it should work.
Only key points of my code just from the top of my head are presented:
EntityManager em; //injected
[...]
public void someEJBMethod() {
[...]
em.persist(someObject);
[...]
Session session = JpaHelper.getEntityManager(em).getActiveSession();
[...]
session.executeQuery(query, args);
[...]
if (someCondition) {
throw new EJBException();
}
[...]
}
And my problem is that when EJBException is thrown database changes caused by em.persist are rollbacked but changes caused by session.executeQuery are committed.
Is it expected behaviour?
I'm using Glassfish 3.1.2, EclipseLink 2.3.2 with Oracle database
Update (test case added)
I've created working test case to show the problem
First database objects:
create table txtest
(id number not null primary key,
name varchar2(50) not null);
create or replace function txtest_create(p_id number, p_name varchar2) return number is
begin
insert into txtest
(id, name)
values
(p_id, p_name);
return p_id;
end;
Definition of a database connection (from domain.xml)
<jdbc-connection-pool driver-classname="" datasource-classname="oracle.jdbc.pool.OracleConnectionPoolDataSource" res-type="javax.sql.ConnectionPoolDataSource" description="" name="TxTest">
<property name="User" value="rx"></property>
<property name="Password" value="rx"></property>
<property name="URL" value="jdbc:oracle:thin:#test:1529:test"></property>
</jdbc-connection-pool>
<jdbc-resource pool-name="TxTest" description="" jndi-name="jdbc/TxTest"></jdbc-resource>
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="txTest">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/TxTest</jta-data-source>
<class>txtest.TxTest</class>
</persistence-unit>
</persistence>
session bean:
#Stateless
public class TxTestBean implements TxTestBeanRemote, TxTestBeanLocal {
private static Logger log = Logger.getLogger(TxTestBean.class.getName());
#PersistenceContext(unitName="txTest")
EntityManager em;
#SuppressWarnings({ "unchecked", "rawtypes" })
#Override
public void txTest(boolean throwException) {
TxTest t = new TxTest();
t.setId(1L);
t.setName("em.persist");
em.persist(t);
Session session = JpaHelper.getEntityManager(em).getActiveSession();
log.info("session : " + String.valueOf(System.identityHashCode(session)));
PLSQLStoredFunctionCall call = new PLSQLStoredFunctionCall();
call.setProcedureName("txtest_create");
call.addNamedArgument("p_id", JDBCTypes.NUMERIC_TYPE);
call.addNamedArgument("p_name", JDBCTypes.VARCHAR_TYPE, 50);
call.setResult(JDBCTypes.NUMERIC_TYPE);
ValueReadQuery query = new ValueReadQuery();
query.setCall(call);
query.addArgument("p_id");
query.addArgument("p_name");
t = new TxTest();
t.setId(2L);
t.setName("session.executeQuery");
List args = new ArrayList();
args.add(t.getId());
args.add(t.getName());
Long result = ((Number)session.executeQuery(query, args)).longValue();
//added to see the state of txtest table in the database before exception is thrown
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("result=" + result.toString());
if (throwException) {
throw new EJBException("Test error #1");
}
}
}
entries from server.log when txTest(true) is invoked:
[#|2012-05-21T12:04:15.361+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.connection|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|client acquired: 21069550|#]
[#|2012-05-21T12:04:15.362+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.transaction|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|TX binding to tx mgr, status=STATUS_ACTIVE|#]
[#|2012-05-21T12:04:15.362+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.transaction|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|acquire unit of work: 16022663|#]
[#|2012-05-21T12:04:15.362+0200|FINEST|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.transaction|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|persist() operation called on: txtest.TxTest#11b9605.|#]
[#|2012-05-21T12:04:15.363+0200|INFO|glassfish3.1.2|txtest.TxTestBean|_ThreadID=167;_ThreadName=Thread-2;|session : 16022663|#]
[#|2012-05-21T12:04:15.364+0200|FINEST|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.query|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|Execute query ValueReadQuery()|#]
[#|2012-05-21T12:04:15.364+0200|FINEST|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.connection|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|Connection acquired from connection pool [read].|#]
[#|2012-05-21T12:04:15.364+0200|FINEST|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.connection|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|reconnecting to external connection pool|#]
[#|2012-05-21T12:04:15.365+0200|FINE|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.sql|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|
DECLARE
p_id_TARGET NUMERIC := :1;
p_name_TARGET VARCHAR(50) := :2;
RESULT_TARGET NUMERIC;
BEGIN
RESULT_TARGET := txtest_create(p_id=>p_id_TARGET, p_name=>p_name_TARGET);
:3 := RESULT_TARGET;
END;
bind => [:1 => 2, :2 => session.executeQuery, RESULT => :3]|#]
[#|2012-05-21T12:04:15.370+0200|FINEST|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.connection|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|Connection released to connection pool [read].|#]
[#|2012-05-21T12:04:35.372+0200|INFO|glassfish3.1.2|txtest.TxTestBean|_ThreadID=167;_ThreadName=Thread-2;|result=2|#]
[#|2012-05-21T12:04:35.372+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.transaction|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|TX afterCompletion callback, status=ROLLEDBACK|#]
[#|2012-05-21T12:04:35.372+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.transaction|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|release unit of work|#]
[#|2012-05-21T12:04:35.372+0200|FINER|glassfish3.1.2|org.eclipse.persistence.session.file://txTest/_txTest.connection|_ThreadID=167;_ThreadName=Thread-2;ClassName=null;MethodName=null;|client released|#]
[#|2012-05-21T12:04:35.373+0200|WARNING|glassfish3.1.2|javax.enterprise.system.container.ejb.com.sun.ejb.containers|_ThreadID=167;_ThreadName=Thread-2;|EJB5184:A system exception occurred during an invocation on EJB TxTestBean, method: public void txtest.TxTestBean.txTest(boolean)|#]
[#|2012-05-21T12:04:35.373+0200|WARNING|glassfish3.1.2|javax.enterprise.system.container.ejb.com.sun.ejb.containers|_ThreadID=167;_ThreadName=Thread-2;|javax.ejb.EJBException: Test error #1
What surprised me the most is that when I checked txtest table during this 20 sec. sleep the record (2,"session.executeQuery") was already there.
It seems like session.executeQuery somehow commits its work (but not the whole transaction).
Can someone explain this behaviour?
I'm not sure what JpaHelper.getEntityManager(em).getActiveSession(); is supposed to do exactly, but it seems likely this doesn't return a container managed entity manager. Depending on how it's exactly implemented, this may not participate in the ongoing (JTA) transaction.
Normally though, transactional resources all automatically participate in the ongoing JTA transaction. In broad lines they do this by checking if there's such on ongoing transaction, and if there indeed is, they register themselves with this transaction.
In EJB, REQUIRES_NEW is not the only mode that can start a transaction 'REQUIRES' (the default) also does this incase the client didn't start a transaction.
I've got it solved!!!
It turned out that EclipseLink uses read connection pools for processing read queries (and obviously that kind of pools uses autocommit or even don't use transactions at all) and default connection pools for data modification queries. So what I had to do was changing:
ValueReadQuery query = new ValueReadQuery();
into
DataModifyQuery query = new DataModifyQuery();
and it works like a charm.
Update
DataModifyQuery doesn't allow to get the result of the function. It returns the number of modified rows. So I got back to ValueReadQuery but used configuration parameter in persistance.xml
<property name="eclipselink.jdbc.exclusive-connection.mode" value="Always"/>
This parameter tells EclipseLink to use default connection pool for both reads and writes.