0

I am using embedded glassfish (3.1.2.2) with junit (4.11), with JDK 1.7, though my source and target is set to 1.6 (maven-compiler-plugin configuration).

Following is my code:

Person.java

@Entity
public class Person implements Serializable {

    private static final long serialVersionUID = 81398385247591972L;

    @Id
    @GeneratedValue
    private Long id;
    @Version
    private Long version;
    @Column(length = 15, nullable = false, unique = true, updatable = false)
    private String username;
    @Column(length = 50)
    private String status;

    // Constructors

    // getters/setters

    // hashCode, equals, toString

}

Service.java

@Stateless
public class Service {

    @PersistenceContext(unitName = "ExamplePU", type = PersistenceContextType.TRANSACTION)
    private EntityManager em;

    public Person add(Person person) {
        em.persist(person);
        return person;
    }

    public Person find(Long id) {
        return em.find(Person.class, id);
    }

    public Person modify(Person person) {
        return em.merge(person);
    }

    // some more code ...

}

ServiceTest.java

public class ServiceTest {

    private static EJBContainer ejbContainer;
    private static Service service;

    // @BeforeClass, @AfterClass, @Before, @After

    @Test
    public void testMerge() {
        Person person;

        /* Step 1 */person = service.add(new Person("username", "status"));
        print("Added : " + person);

        person.setStatus("Away");
        /* Step 2 */person = service.modify(person);
        print("Merged (status change) : " + person);

        person.setUsername("UsErNaMe");
        /* Step 3 */person = service.modify(person);
        print("Merged (username change) : " + person);
    }

    // Some more tests

}

Step 1 generates following SQL (as expected):

INSERT INTO PERSON (ID, STATUS, USERNAME, VERSION) VALUES (?, ?, ?, ?)
    bind => [1, status, username, 1]

Step 2 generates following SQL (as expected):

UPDATE PERSON SET STATUS = ?, VERSION = ? WHERE ((ID = ?) AND (VERSION = ?))
    bind => [Away, 2, 1, 1]

Step 3 does not generate any SQL, but it does not throw any exception, which I am expecting, as the 'username' is annotated as @Column(..., updatable = false). The print(...) method prints following output:

Merged (username change) : Person [id=1, version=2, username=UsErNaMe, status=Away]

This time the merge() operation has updated username, but not version. Also, now the database is out-of-sync with EntityManager cache.

Is this expected, or bug in EclipseLink?

UPDATE

Expected result is exception at Step 3 above.

UPDATE

Have filed bug here.

Atul Acharya
  • 497
  • 2
  • 8
  • 21

1 Answers1

1

You marked the column as non-updatable, and EclipseLink detects that the only change made to the person you tell it to merge is the user name. But the user name must not be updated. So it doesn't issue any SQL update query.

If you mark a column as non-updatable, you shouldn't update it.

So, to make things clear, the behavior you observe is the expected behavior.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • I'm not using Hibernate. It's EclipseLink, comes with GlassFish. Secondly, username being non-updatable is the reason I'm expecting exception in Step 3. Sorry, if my question was not clear enough. – Atul Acharya Feb 27 '13 at 09:22
  • Sorry. I'm so used to Hibernate that I typed Hibernate instead of EclipseLink. But it doesn't change anything. They're both implementations of JPA. You expect an exception, but you won't get one. `updatable=false` simply means: this column must be ignored for update statements. It doesn't mean: "throw an exception if I ask you to merge an entity and this column has changed". – JB Nizet Feb 27 '13 at 09:36
  • You are right about 'ignore for update statements' part. But the object it returns is now inconsistent with the database (as shown in Step 3 in question). Also, later on, if I do a find(...), it will return me a wrong object, as that's what it has in cache. I think, this is not right. If the field is ignored, it needs to be ignored for both update statement, as well as merging the state. – Atul Acharya Feb 27 '13 at 10:18
  • I haven't seen anything explicitely specified in the spec regarding that. To me, it's your job to avoid updating a field which is not updatable. – JB Nizet Feb 27 '13 at 10:21
  • You are right, spec does not talk about exception. But waht is disturbing is, it returns object which never persisted, and never will. So, it should atleast return the un-updated object, consistent with database. As for avoiding updates being developers job, what is the purpose of this annotation-attribut? If I can avoid updates, I do not need this annotation at all! Any ways, thanks for your inputs. I'm filing a bug for this behaviour. - Atul – Atul Acharya Mar 01 '13 at 10:54
  • Some column values are immutable: they're decided at insertion time, and should never change afterwards. In this case using updatable=false makes sense. But since it should never change, changing it inside your code shouldn't be done either. updateable=false is also necessary when the same column is mapped twice: once as an association, and one as a rgular column, for example. In this case, one of them should not be part of the update statement. – JB Nizet Mar 01 '13 at 11:37