Shouldn't EntityManager#merge work with non identifiable entity?

62 Views Asked by At

I'm designing JAX-RS APIs.

POST /myentities
PUT  /myentities/{myentityId}

I did like this.

@PUT
@Path("/{myentityId: \\d+}")
@Consumes(...)
@Transactional
public Response updateMyentity(
    @PathParam("myentityId") final long myentityId,
    final Myentity myentity) {
    if (!Objects.equal(myentitiyId, myentity.getId())) {
        // throw bad request
    }
    entityManager.merge(myentity);
    return Response.noContent().build();
}

I suddenly get curious and questioned to my self.

When a client invokes following request

PUT /myentities/101 HTTP/101
Host: ..

<myentity id=101>
</myentity>

Is it possible that the request is processed even if there is no resource identified by 101?

I ran a test.

acceptEntityManager(entityManager -> {
    // persist
    final Device device1 = mergeDevice(entityManager, null);
    entityManager.flush();
    logger.debug("device1: {}", device1);
    assertNotNull(device1.getId());
    // remove
    entityManager.remove(device1);
    entityManager.flush();
    assertNull(entityManager.find(Device.class, device1.getId()));
    // merge again, with non existing id
    final Device device2 = entityManager.merge(device1);
    entityManager.flush();
    logger.debug("device2: {}", device2);
});

I found that second merge works and a new id assigned. Is this normal? Shouldn't EntityManager#merge deny the operation?

Is it mean that any client can attack the API by calling

PUT /myentities/<any number>

?

1

There are 1 best solutions below

0
On

If I understand this right, then yes, this is how merge should work. merge creates a new instance of your entity class, copies the current state of the entity that you provide (in your case device1, and adds that NEW instance to the transaction. So the NEW instance is managed, and any changes you make to the old instance are not updated in the transaction and will not be propagated to the database via flush - operation.

So while you removed device1 from the database with entityManager.remove(device1); the call
final Device device2 = entityManager.merge(device1); adds a new copy of it (with new ID) to the database. Changes you make to device1 are not tracked in the transaction, so they won't be reflected in the database tables once you flush. Changes to device2 on the other hand WILL be tracked in the transaction.