5

I have the following simple webservice declared as @Stateless EJB running on GlassFish 3.1.2.2 with EclipseLink 2.4.1 using a JTA DataSource to connect to a MySQL database:

@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response update(TimeRow e) throws Exception {
    if ((e.getKey() == null) || !e.getKey().keyValid()) {
        return Response.status(400).build();
    }

    TimeRow existing = em.find(TimeRow.class, e.getKey());
    if (existing == null) {
        em.persist(e);
    } else {
        existing.setValues(e.getValues());
        em.flush();
    }
    return Response.status(204).build();
}

Entity class of TimeRow:

@Entity
@NamedQueries({
    @NamedQuery(name="TimeRow.findAllByUser",
         query="SELECT t FROM TimeRow t WHERE t.table.userId = :uid")
})
public class TimeRow implements Serializable {

    @EmbeddedId
    private TimeRowPK key;

    @MapsId("userId")
    @JoinColumn(name = "USERID", referencedColumnName = "userId")
    @ManyToOne
    private UserTable table;
    @Column(name="ROWVALUES")
    private List<Double> values;

    public TimeRow() {
        this.key = new TimeRowPK();
        this.values = new ArrayList<Double>(20);
        extendValuesTo20();
    }

    public TimeRow(String uid, Date date) {
        this.key = new TimeRowPK(date, uid);
        this.table = new UserTable(uid);
        this.values = new ArrayList<Double>(20);
        extendValuesTo20();
    }

    public List<Double> getValues() {
        return values;
    }

    public void setValues(List<Double> values) {
        this.values = values;
        extendValuesTo20();
    }

    private void extendValuesTo20() {
        if (this.values.size() < 20) {
             for (int i = this.values.size(); i < 20; i++) {
                  this.values.add(0.0);
             }
        }
    }

}

@EmbeddableId TimeRowPK:

@Embeddable
public class TimeRowPK implements Serializable {

    public TimeRowPK() { }

    public TimeRowPK(Date date, String userId) {
        this.date = date;
        this.userId = userId;
    }

    @Column(name="DAY")
    private Date date;
    @Column(name = "USERID")
    private String userId;

    public boolean keyValid() {
        return ((date != null) && ((userId != null) && !userId.isEmpty()));
    }

}

persistence.xml (without the <persistence> tag):

<persistence-unit name="test" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/test</jta-data-source>
    <class>com.test.TimeRow</class>
    <class>com.test.TimeRowPK</class>
    <class>...</class>
    <properties>
        <property name="eclipselink.ddl-generation" value="create-tables" />
        <property name="eclipselink.ddl-generation.output-mode" value="database" />
    </properties>
</persistence-unit>

Webservice declaration:

@Path("row")
@Stateless
public class TimeRowWebService {

    @PersistenceContext(unitName = "test")
    private EntityManager em;

    ...

}

The problem is that if the entity exists, the changes get stored only in the PersistenceContext, but they are not committed to the database. Meaning that I can retrieve the correct data with the changes, but for example if I restart the AS, the changes are gone. There are no errors in the AS log.

So I guess I have to do some bean level manual transaction handling to get this work. What exactly do I have to add to get this working?

Daniel Szalay
  • 4,041
  • 12
  • 57
  • 103
  • Is the bean declared @Stateless? – Carlo Pellegrini Apr 12 '13 at 07:44
  • 1
    In this case I can't see an error here... `@Stateless` implies `@TransactionAttribute(REQUIRED)` if you don't change it... Maybe it resides in the `MyEntity.setValues()` or in the `MyEntity` declaration itself. Can you post the `MyEntity` code? – Carlo Pellegrini Apr 12 '13 at 07:59
  • @CarloPellegrini I've added the actual entity class with it's `@EmbeddedId` class. I've removed some getters to make it shorter. – Daniel Szalay Apr 12 '13 at 08:32
  • 1
    What type of EntityManager are you using ? Application or Container-managed one ? – Gab Apr 12 '13 at 08:34
  • Show us how you obtain EntityManager, how does the persistence.xml looks like and the WebService class annotations (or DD). – Piotr Nowicki Apr 12 '13 at 08:49
  • @Gab Container-managed if I'm not mistaken. I use @PersistenceContext to inject, and I get an exception if I try to manually call `em.getTransaction().begin()` saying I am not allowed to use `EntityTransaction` if using a JTA datasource. – Daniel Szalay Apr 12 '13 at 08:50
  • I don't really remember about persisting lists of basic types - can you e.g. try adding `@ElementCollection` to your values property? (don't remember if it's added implicitly or not) The transaction should be committed during business method execution, so it should be working... – Piotr Nowicki Apr 12 '13 at 09:31
  • @PiotrNowicki Tried `@ElementCollection`, but I got the same error. Also strangely enough the list is reversed :) – Daniel Szalay Apr 12 '13 at 09:41
  • Can it be a problem that I'm injecting with `@PersistenceContext` in multiple webservices? – Daniel Szalay Apr 12 '13 at 09:52
  • No it should not be a problem. Can you test if some basic value is updated? Right now you're testing it only with list of basic types; can you edit some direct field of your entity (add one for tests) and see if it gets updated and persisted? – Piotr Nowicki Apr 12 '13 at 10:02
  • 1
    No Container properly manage EM scope across the multiple injection points. The right question is what is the JTA transaction scope in a restfull app. JPA JSR extract : When a container-managed entity manager is used, the lifecycle of the persistence context is always managed automatically, transparently to the application, and the persistence context is propagated with the JTA transaction – Gab Apr 12 '13 at 10:08
  • You can't commit which means there is a rollback of the transaction which means there is definitely an error. Can you please enable logging in persistence ? Regards, – Ravi Trivedi Apr 14 '13 at 00:20
  • @PiotrNowicki Tried it with a basic `Double` field, and had the same phenomenon. Also, I noticed that the value change gets consolidated in DB if I change in the same 'session' where the entity was persisted. – Daniel Szalay Apr 15 '13 at 09:21
  • I suspect that this is something with the `@EmbeddedId`. I have another entity class which uses an auto-generated `@Id` with a `List` property and it works properly. I will try to change `@EmbeddedId` to a composite key implementation with `@Id`. – Daniel Szalay Apr 15 '13 at 09:30
  • Perhaps it has something do to with your `date` part of embeddedPK? – Piotr Nowicki Apr 15 '13 at 10:25

2 Answers2

2

After struggling to get this working the @EmbeddedId way, I've tried to implement it by using a generated @Id, and adding a unique constraint for the two key fields afterwards.

Entity

public class TimeRow implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @JoinColumn(name = "USERID", referencedColumnName = "USERID")
    @ManyToOne(optional = false)
    private UserTable table;

    @Basic(optional = false)
    @Column(name = "DAY")
    @Temporal(TemporalType.DATE)
    private Date date;

    @Lob
    @Column(name="ROWVALUES")
    private List<Double> values;

    ...
}

Additionally, I've changed the DB engine from MyISAM to InnoDB to have better foreign key support. To do this, I've added default-storage-engine = innodb to /etc/mysql/my.cnf under the [mysqld] section.

After generating the DB structure with DDL, I've added a unique constraint for USERID and DAY:

alter table TIMEROW add unique index(USERID,DAY);

Now it works and data is modified correctly :) A huge thank you to everybody who contributed to this question!

Community
  • 1
  • 1
Daniel Szalay
  • 4,041
  • 12
  • 57
  • 103
1

Maybe the catch is here:

public void setValues(List<Double> values) {
    this.values = values;
    extendValuesTo20();
}

Try to change it to use the container's list as in:

public void setValues(List<Double> values) {
    this.values.clear();
    this.values.addAll(values);
    extendValuesTo20();
}

Also, it's a very strange use of JPA. I've never seen lists persisted this way without @ElementCollection

Carlo Pellegrini
  • 5,656
  • 40
  • 45