Richard Zowalla created OPENJPA-3019:
----------------------------------------
Summary: In-place change to an element of a collection converted
as a whole is not written
Key: OPENJPA-3019
URL: https://issues.apache.org/jira/browse/OPENJPA-3019
Project: OpenJPA
Issue Type: Bug
Reporter: Richard Zowalla
A change made in place to an element of a collection that is converted as a
whole is not written to the database. Given
{code:java}
@Convert(converter = ItemListConverter.class) //
AttributeConverter<List<Item>, String>
private List<Item> items = new ArrayList<>();
{code}
the following commits without an {{UPDATE}}, and the change is lost:
{code:java}
em.getTransaction().begin();
em.find(InPlaceConvertedEntity.class, 1L).getItems().get(0).setName("changed");
em.getTransaction().commit();
{code}
Adding or removing an element is written. So is a change made in place to a
converted attribute that is not a collection, e.g. {{@Convert(converter =
ItemConverter.class) private Item item}} followed by
{{getItem().setName("changed")}}. Only the collection case is affected. The
collection is wrapped in a proxy that tracks adds and removes, and the field is
only marked dirty through that proxy, so a change inside an element is never
seen.
The specification requires the change to be written. Section "Type Conversion
of Basic Attributes": "A converted attribute is considered a basic attribute".
Section "Synchronization to the Database": "A modification to the state of an
entity might include an assignment of a new value to a persistent property or
field of the entity, or even modification of a mutable value of a persistent
property or field."
Mapping a collection converted as a whole only works with OPENJPA-3017 (PR
#220). Reported there by Francesco Chicchiriccò while testing Apache Syncope
against 4.2.0-SNAPSHOT: changing a value of a {{PlainAttr}} held in a converted
{{List<PlainAttr>}} and merging the user produced no {{UPDATE}}, while removing
the attribute, changing it and adding it back did.
Test case, on top of OPENJPA-3017, package
{{org.apache.openjpa.persistence.conversion}}:
{code:java}
@Converter
public class ItemConverter implements AttributeConverter<Item, String> {
public String convertToDatabaseColumn(Item attribute) {
return attribute == null ? null : attribute.getName();
}
public Item convertToEntityAttribute(String dbData) {
return dbData == null ? null : new Item(dbData);
}
}
@Entity
public class InPlaceConvertedEntity {
@Id
private long id;
@Convert(converter = ItemListConverter.class)
private List<Item> items = new ArrayList<>();
@Convert(converter = ItemConverter.class)
private Item item;
// getters and setters
}
public class TestConvertedInPlaceChange extends SingleEMFTestCase {
public void setUp() {
setUp(InPlaceConvertedEntity.class, CLEAR_TABLES);
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
InPlaceConvertedEntity e = new InPlaceConvertedEntity();
e.setId(1L);
e.getItems().add(new Item("one"));
e.getItems().add(new Item("two"));
e.setItem(new Item("single"));
em.persist(e);
em.getTransaction().commit();
}
}
// fails: expected:<[changed]> but was:<[one]>
public void testElementOfConvertedListChangedInPlace() {
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
em.find(InPlaceConvertedEntity.class,
1L).getItems().get(0).setName("changed");
em.getTransaction().commit();
}
try (EntityManager em = emf.createEntityManager()) {
assertEquals("changed",
em.find(InPlaceConvertedEntity.class,
1L).getItems().get(0).getName());
}
}
// passes
public void testConvertedValueChangedInPlace() {
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
em.find(InPlaceConvertedEntity.class,
1L).getItem().setName("changed");
em.getTransaction().commit();
}
try (EntityManager em = emf.createEntityManager()) {
assertEquals("changed", em.find(InPlaceConvertedEntity.class,
1L).getItem().getName());
}
}
// passes
public void testConvertedListChangedStructurally() {
try (EntityManager em = emf.createEntityManager()) {
em.getTransaction().begin();
em.find(InPlaceConvertedEntity.class, 1L).getItems().add(new
Item("three"));
em.getTransaction().commit();
}
try (EntityManager em = emf.createEntityManager()) {
assertEquals(3, em.find(InPlaceConvertedEntity.class,
1L).getItems().size());
}
}
}
{code}
{{Item}} is a plain {{Serializable}} bean with a {{name}} property, and
{{ItemListConverter}} joins the names with commas, as in the tests of
OPENJPA-3017.
Possible fix: at flush, compare the converted database value of such a field
with the value it had when it was loaded, and mark the field dirty when they
differ. Alternatively, do not proxy a collection that is converted as a whole
and treat it like the non-collection case.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)