Prevent unwanted cascaded table updates

2019-08-05 08:57发布

问题:

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?

回答1:

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



回答2:

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.