I have a form - Workflow where there are fields like wfName, assignedUser, dueDate, turnAroundTime. etc.
It is backed by an entity Workflow with a reference to the User entity as Many-to-One.
When a change is made to the assignedUser field( it is an email address) and the form is submitted, I get a Unique-constraint violation error on the USER entity.
I am not trying to achieve this. I only want to replace the User in the Workflow entity.
The save function is performed by a Stateful session bean, with an EXTENDED persistence context.
Am I missing something here? Is this the correct way to updated information in a referenced field?
While setting the updated User I am doing
User user = workflow.getUser();
//This user has its email address changed on the screen so getting a fresh reference of the new user from the database.
user = entitManager.createQuer("from User where email_address=:email_address").setParameter("email_address", user.getEmailAddress).getSingleResult();
//This new found user is then put back into the Workflow entity.
workflow.setUser(user);
entityManager.merge(workflow);
No exception is thrown at the time these lines are executed, but later in the logs I find that it threw a
Caused by: java.sql.SQLException: ORA-00001: unique constraint (PROJ.UK_USER_ID) violated
There is no cascading configuration present in the entities.
The following is the association code for the entities-
The workflow-User relation
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", nullable = false)
#NotNull
public GwpsUser getUser() {
return user;
}
public void setUserByUserId(User user) {
this.user = user;
}
The User-Workflow Relation
#OneToMany(fetch = FetchType.LAZY, mappedBy = "User")
public Set<Workflow> getWorkflowsForUserId() {
return workflowsForUserId;
}
public void setWorkflowsForUserId(
final Set<Workflow> WorkflowsForUserId) {
this.workflowsForUserId = workflowsForUserId;
}
In the SFSB I have two methods loadWorkflow() and saveWorkflow().
#Begin(join = true)
#Transactional
public boolean loadProofData(){
//Loading the DataModel here and the conversation starts
}
If I add flushMode = FlushModeType.MANUAL inside #Begin. The saveWorkflow() method saves the data properly, only for the first time. I have to go somewhere else and then come back to this page if I want to make any further changes.
The saveWorkflow() method looks like
#SuppressWarnings("unchecked")
public boolean saveWorkflow() throws FileTransferException {
//Do some other validations
for (Workflow currentWorkflow : workflowData) {
User user = currentWorkflow.getUser();
//This user has its email address changed on the screen so getting a fresh reference of the new user from the database.
user = entitManager.createQuery("from User where email_address=:email_address").setParameter("email_address", user.getEmailAddress).getSingleResult();
//This new found user is then put back into the Workflow entity.
currentWorkflow.setUser(user);
}
//Do some other things
}
Not using the merge() method here, but still the problem persists.
Why are you calling merge? Is the workflow detached (serialized)?
If it is not detched, you should not call merge, just change the object and it should be updated.
You should have a setUser method, not setUserByUserId? Not sure how this is working, perhaps include your full code. Your get/set method might be corrupting your objects, in general it is safer to annotate fields instead of method to avoid code in your get/set method to cause odd side-effects.
Ensure you are not creating two copies of the object, it seems your merge is somehow doing this. Enable logging and include the SQL. Calling flush() directly after your merge will cause any errors to be raise immediately.
Related
I have a Spring Boot application and two entities, User and Role that look like the following:
#Entity
public class User {
// other fields
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
#JsonDeserialize(using = RolesJsonDeserializer.class)
#NotEmpty
private Set<Role> roles = new HashSet<>();
// getters and setters
}
#Entity
public class Role {
// other fields
#ManyToMany(mappedBy = "roles")
private List<User> users;
#PreRemove // whenever role is removed, remove association with each user
private void removeRolesFromUsers() {
for (User u : users) {
u.getRoles().remove(this);
}
}
// getters and setters
}
And then I have a Spring JPA integration test annotated with #DataJpaTest and #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) which is tested against PostgreSQL database. I want to assert that before role is deleted, all associated users are dissociated from that role before it is removed from database.
#Test
#Transactional
public void whenRoleIsDeleted_thenItIsDeletedFromAllUsersThatHadIt() {
clearDatabase(userRepository, roleRepository);
populateDatabase(USERS_COUNT, null, userRepository, roleRepository); // saves a bunch of pre-defined roles first, then saves users associated with those roles
entityManager.flush();
Role existingRole = randomExistingRole(); // obtains role that exists
System.out.println(existingRole.getUsers());
Condition<RoleRepository> existingRole_ = new Condition<>(repo -> repo.findRoleByName(existingRole.getName())
.isPresent(), "hasExistingRole");
assertThat(roleRepository).has(existingRole_);
entityManager.flush();
roleRepository.delete(existingRole);
// then assert that no user has existingRole in its associated roles
}
But my assertion couldn't complete as NullPointException is thrown when I try to remove existingRole. This exception does not happen when application is used normally. It is thrown because role has no associations to any user (and is actually a null), even though I specify that it is Role's users is mappedBy = "roles". User on the other hand has roles in its roles field. If I try to set a break point before roleRepository.delete(), I can see that there are no changes made to the actual database until the end of test method, which I suspect is the problem. Although in logs I can see that Hibernate populates the database and creates joining table just fine.
I tried the following:
Set entityManager.setFlushMode(FlushModeType.COMMIT) to force every flush to commit a transaction, that didn't work.
Removed #Transactional annotation from test method, that didn't work.
Tried #Rollback(false) annotation, that didn't work, but it confirmed that if database is populated before test method starts, then role has users associated with it and made me think that presence of users in actual database is important for this test to work as I expect it to.
I want to understand:
Why does this happen only in tests?
Why can't I see new users and roles in my database immediately after I commit a transaction, but only once test method finished executioe despite the absence of #Transactional?
EDIT
I solved this problem by adding logic to associate role with user manually when user.setRoles() is invoked. I don't know if this is right way? Why won't Hibernate do this automatically?
public void setRoles(Set<Role> roles) {
this.roles = roles;
roles.forEach(role -> {
if (!role.getUsers().contains(this)) {
role.getUsers()
.add(this);
}
});
}
I'm currently working on a project and I want to see what damage it can do if I don't embrace my code with try-catch block when persisting object into database. Here is my code down below that i use as test.
public class NewEventServiceBean implements NewEventService {
#PersistenceContext(name = "example")
EntityManager manager;
#Resource
private UserTransaction userTransaction;
#Override
public void createNewEvent(String title, String content) throws Exception
{
userTransaction.begin();
Event event = new Event();
Content cont = new Content();
cont.setContent(content);
event.setTitle(title);
event.setCont(cont);
manager.persist(event);
manager.persist(cont);
userTransaction.commit();
}
In the database i have this Event table that has a foreign key to Content table.
And my question is if It's possible that Event object is persisted in to the database even if I cause something wrong when persisting the content class. Or what is the disadvantages of not embracing the code with try catch and rollback?
I've tried to cause an error when persisting the content object, but the Event is not persisted into the datbase even if everything is correct in that class.
I have this API which deletes and adds different users into the the Database. The way this API is written is something like this:
/* Assuming 'users' is an array of users to delete (if users[x].toDelete == true),
or to add otherwise */
using (var db = new myContext())
{
using (var dbTransaction = db.Database.BeginTransaction())
{
try
{
/* Performing deletion of Users in DB */
SaveChanges();
/* Performing add of Users in DB */
foreach(user in users)
if (!user.toDelete) {
db.users.add(..);
db.SaveChanges();
}
dbTransaction.Commit();
}
catch (ValidationException ex)
{
dbTransaction.Rollback();
}
}
}
The reason why I use a transaction is that I have some validations that should be run on the new data.. For example, I cannot add two user with the same email!
All the validation Server-Side is done with Data-Annotations and the MetaData classes, therefore I created the Attribute Uniqueness which I associated to the property email of the class UserMetaData.
So, the problem is that, inside the Uniqueness Attribute I need to check again the database to search for other users with the same email:
public class IsUnique : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
...
using (var db = new myContext()) {
/* Here I perform a select on the DB looking for records
with the same email, all using Reflection. */
if (recordFound > 0)
return new ValidationResult("email already exists");
return ValidationResult.Success;
}
}
}
So, as you can see, inside the validation I use new myContext() and that's where the problem is: let's pretend I have an empty database and I add two user with the same email in the same query.
As you can see, I call the db.SaveChanges() after every user has been added, therefore I thought that, when the second user to be added would have been validated, my validation would have known that the first user added and the second one have the same email, therefore an Validation Exception would have been throw and the dbTransaction.Rollback() called.
The problem is that inside the validator i call new myContext() and inside that context the changes that I thought would have been there thanks to the db.SaveChanges() there arent't, and that's I think is because all the changes are inside a Transaction.
Sooo.. The solutions I thought so far are two:
Nested Transaction: in the API I have an outer transaction which save the state of the DB at the beginning, then some inner transaction are run every time a db.SaveChanges() is called. Doing this, the validation does see the changes, but the problem is that, if in the catch i called outerTransaction.rollback(), the changes made by the inner transactions are not rollbacked;
Some way to retrieve the context I have used in the API even in the validation.. But in there I only have these two arguments, so I don't know if it's even possible;
We are using the Entity Framework 4.3.1 with POCO classes (no proxies).
Our User class stores an email address which should be not be used by any other user.
The following test is used to check the validation of this:
[TestMethod]
public void UserEmailNotUniqueTest()
{
// arrange
// - create user one and store in db
User userOne = TIF.GetUser(model, true);
// - create user two and store in db
User userTwo = TIF.GetUserTwo(model, true);
// act
// - change user one email to user two email
userOne.EmailAddress = userTwo.EmailAddress;
// - save
model.SaveChanges();
The test initialize creates an empty database and hooks it up to the “model” DbContext decendant. The TIF class creates the test instances of users and stores them in database. This works, both users are present in the database.
This validation requires the state of the database to be unchanged, so we have an overridden SaveChanges method so we can pass in serializable transaction:
public virtual int SaveChanges(bool commitWhenDone)
{
try
{
saving = true;
int result;
TransactionScope scope;
using (scope = new TransactionScope( modelTransaction.Get() ))
{
//… open connection etc.
ChangeTracker.DetectChanges();
IEnumerable<DbEntityValidationResult> validationResults =
GetValidationErrors();
//… handle the errors
result = base.SaveChanges();
The test fails as not any change is detected. No validation code is executed. Inspecting model.Entity(userOne).State returns “Unchanged”, and yet the CurrentValues contains the proper value i.e. the userOne has the email address of userTwo.
What are we missing?
I have two entity classes user and device.
User entity:
public class User {
private Long userId;
#OneToMany( mappedBy = "userId", fetch = FetchType.LAZY)
private Collection<Device> deviceCollection;
and device entity:
public class Device implements Serializable {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private User userId;
When I merge a previously detached device entity into the entity manager after the parent user has been deleted, both the (previously removed) user and the device are re-inserted into the database. There is no cascade annotation on user or device entity; therefore, I don't expect the user entity to be reinserted but it did;
How do I prevent the merge operation to cascade to the user entity?
Thanks in advance.
Any changes you do in detached state there is no possible way for Session Manager to know it so for it the changes are always new objects that needs to be merged (If you are calling merge)
So when you call merge it will load it from database so your object will have Prev+ new changes. So that is why mentioned behavior is happening.
What you can do is first load entity in the session apply changes and then call merge.
What you can do is something like below I have used similar relationship in one of my project with Eclipse Link
Query query = entityManager
.createNamedQuery("User.FindByUserId");
User fromDatabase = null;
try {
query.setParameter("userId", device.getUser().getUserId());
fromDatabase = (User) query.getSingleResult();
} catch (NoResultException noResultException) {
// There is no need to do anything here.
}
if (fromDatabase == null) {
User user= entityManager.merge(device.getUser());
device.setUser(user);
} else {
device.setUser(user);
}
entityManager.persist(device);
Try adding insertable=false, updatable=false to your JoinColumn, e.g.
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable=false, updatable=false)
You should be using a version number to prevent entities from being mistakenly resurected. This will force an exception, where as the specification is a bit unclear on what should happen when merging over a relation that isn't marked cascade all or merge. The spec states that managed entities will be synchronized to the database, while the section dealing with merge implies that even entities referenced by relations without the cascade merge/all options will be managed afterward. This behavior is probably not what was intended, but shouldn't be relied on until clarified.
I had the same problem
and I found a bug about this: EntityManager.merge() cascading by default
but I really don't understand why this behaviour was never fix. It is one of reasons among others that I don't use EclipseLink (But it's not the point here)
Edit:
Chris, the comment which begin with "I'm not an expert" the argument that is put in head is not right, I think. What I understand, it's just that entity with a relation without cascade=MERGE or cascade=ALL, you can just navigate, that's all.
Otherwise why use Merge annotation ? It doesn't make sense.