I have User class, where I want ManyToMany relation referencing on itself - particalury I am solving that user could follows/be followed another user. There is no need for other attributes, so it should be one table with composite primary key. This is how it is annotated:
#ManyToMany(cascade = {CascadeType.ALL, CascadeType.MERGE})
#JoinTable(name = "user_followers", joinColumns = { #JoinColumn(name = "follower") }, inverseJoinColumns = { #JoinColumn(name = "following") })
private Set<User> following = new HashSet<User>();
#ManyToMany(mappedBy = "following")
private Set<User> followers = new HashSet<User>();
Everything looks fine - table is generated, PK is composite follower_following. When I persist data on first try, it works - insert statement is executed. BUT on the second time, it is still trying to insert previously executed relation (obviously not the same relation) and I get:
Unexpected RuntimeException
Last cause: Duplicate entry 'follower-following' for key 'PRIMARY'
// EDIT: There are correct FK keys instead of follower-following
This is how I add follower and persist user:
if (!user.following.contains(toFollowUser)) {
user.following.add(toFollowUser);
}
if (session.contains(user)) {
session.persist(user);
} else {
session.merge(user)
}
session.getTransaction().commit(); // HERE I get exception
From my point of view, it looks like Hibernate doesn't see that previous relation was peristed and written to database and still try to insert new record that of course falls on duplicated key. Do you have an idea what am I doing wrong?
Related
So I have client = creditor which has list of documents. This list can contain only one type of each document, so i have method add document which adds new documnet, but if there is already document of this type it should be replaced.
this test fail on unique constraint
def "should replace documents with same type"() {
given:
def creditor = creditors.create(CreditorHelper.createSampleCreditorForm())
def documentType = DocumentTypeEvent.INVESTMENT_INSTRUCTION
and:
def old = documents.addDocument(new DocumentForm("urlOld", creditor.creditorReference, documentType, ZonedDateTime.now()))
when:
documents.addDocument(new DocumentForm("urlNew", creditor.creditorReference, documentType, ZonedDateTime.now()))
then:
def newResult = documentRepository.findByCreditorReference(creditor.creditorReference)
newResult.size() == 1
newResult.find {
it.url == "urlNew"
}
and:
documentRepository.findByHash(old.hash) == Optional.empty()
}
implementaion is simple replace:
#Transactional
public Document addDocument(final DocumentForm documentForm) {
return creditorRepository.findByCreditorReferenceIgnoreCase(documentForm.getCreditorReference())
.addDocument(new Document(documentForm));
}
above calls:
public Document addDocument(Document newDocument) {
documents.removeIf(existingDocument -> existingDocument.getType() == newDocument.getType());
documents.add(newDocument);
}
entity:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "creditor_id")
#Builder.Default
private List<Document> documents = new ArrayList<>();
funny is that when I remove unique constraint from flyway test is passing, so it seems like problems with transaction.
I think it might be related to Hibernate's queries ordering during flush time. Because persisting new entities is invoked as first operation by Hibernate's session, you get exception as entity is present in DB during flush time. Turn on show_sql option in Hibernate and try look at logs what is the real order of queries sent to DB.
Also read Vlad's post about ordering: A beginner’s guide to Hibernate flush operation order. You can read code of class EventListenerRegistryImpl as well and see how ordering looks like.
I'm not sure if my question title is correct, if not, please correct it.
Anyway, long story short, I have sellers, each seller belongs to a company, each seller has an ID as a primary key which is auto-incrementing and a seller-number which is unique per company.
id seller-number company-id
0 0 1
1 1 1
2 2 1
3 0 2
4 1 2
4 2 2
Here's my Seller entity:
#Entity
#Configurable
#Table(name="Seller", uniqueConstraints = {#UniqueConstraint(columnNames= {"company", "sellerNumber"})})
public class Seller implements Serializable {
#PersistenceContext
transient EntityManager entityManager;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#ManyToOne
private Company company;
private Long sellerNumber;
...
Now when creating a seller, I do the following:
#Transactional
private void createSeller(SellerRequest request, SellerResponse response, Session session) {
Seller seller = new Seller();
// generate seller number
TypedQuery<Long> query = Seller.entityManager().createQuery("SELECT max(o.sellerNumber) + 1 FROM Seller AS o WHERE o.company=:company", Long.class);
query.setParameter("company", session.getCompany());
Long sellerNumber = query.getSingleResult();
seller.setSellerNumber(sellerNumber == null ? 1 : sellerNumber);
...
seller.setCompany(session.getCompany());
// persist
seller.persist();
...
The seller numbers I'm getting back is fine, until I start doing a lot of concurrent creates. If two creates happen at the exact same moment, I get a org.hibernate.exception.ConstraintViolationException
The requirements are that I only use an ID as a primary key, no composite primary keys. So taking these constraints into account, how should I be creating these entities so that they have unique seller numbers inside their companies and avoid ConstraintViolationExceptions ?
Is using max(o.sellerNumber) + 1 the right way to go or is there a better way to do this?
The hackish way to accomplish this was to simply catch the ConstraintViolationException and recursively retry the create, increment a retry counter in the request so that it's possible to bail if the number of recursive retries becomes too much.
try {
createSeller(...);
} catch(org.springframework.orm.jpa.JpaSystemException e){
if (e.contains(org.hibernate.exception.ConstraintViolationException.class)){
return thisMethodThatCallsCreateSeller(...);
} else {
throw e;
}
}
On small loads, there's almost no contention, when throwing heavy load at it, there's a little bit of a slowdown as contention causes multiple calls to try and create the seller with multiple ConstraintViolations being caught. Gets the job done though.
I know there are many similar questions about this trouble but nothing works for me.
I have #ManyToOne relationship between Aim and User.
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false, updatable = false)
private User user;
and
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Collection<Aim> userAims;
respectively.
#Override
#Transactional(propagation = Propagation.REQUIRED)
#PreAuthorize("isAuthenticated() and principal.user.isEnabled() == true")
public Aim findById(String aimId) throws NumberFormatException, EntityNotFoundException {
Aim aim = null;
try {
aim = aimRepository.findOne(Long.parseLong(aimId));
} catch (NumberFormatException e) {
throw new InvalidDataAccessApiUsageException(e.getMessage(), e);
}
if (aim == null) throw new EntityNotFoundException("Aim with id: " + aimId + " not found!");
return aim;
}
#OneToMany associations work fine with lazy fetching. Method isn't nested to another #Transactional method so #Transactional works fine.
So the record exists.
Classes User and Aim aren't final and implement
Serializable
Some sources advice to put annotations on getters. It also doesn't
work.
#Fetch(FetchMode.SELECT) the same situation =\
Query via Hibernate results the same, but HQL query with left join
fetch works fine
My FK is ON UPDATE CASCADE ON INSERT CASCADE
optional = false also tried...
Pay attention that I haven't the LazyInitException
Thanks in advance!
I'm guessing from the code in your findById method, and by the reference to "lazy initialization not working" in the title, that you are wanting to find an Aim object by it's numeric Id, along with the associated User object.
In order to do this with lazy-loading, you need to 'get' the associated object, and (most importantly) you need to 'get' one of the associated entity's fields.
So the code inside the try block should be:
aim = aimRepository.findOne(Long.parseLong(aimId));
if (aim != null && aim.getUser() != null) {
aim.getUser().getUserId(); // doesn't need to be assigned to anything
}
Alternatively, if you have a logger available you can use the userId in a debug or trace log message:
if (aim != null && aim.getUser() != null) {
logger.debug("Lazy-loaded User " + aim.getUser().getUserId());
}
This has the added benefit that you can debug how things are lazy-loaded.
By the way, we found out the hard way that making a find routine throw an Exception when it doesn't find something is a bad idea. This is because you might want to use the find routine to find out if an Entity does NOT exist. If that is happening within a transaction, your exception may trigger an unwanted rollback (unless you specifically ignore it). Better to return null and check for that instead of using a try ... catch.
Hallo I have a problem with a many-to-many mapping in Hibernate.
The tables shown above, are connected with a many-to-many mapping. The table tbl_cp_group_relation is the table with the n:m connections.
In my entities I solved this problem on several ways. I used the many-to-many mapping and the many-to-one mapping. In both ways it worked partially. I got the groups of a charging point and I got the charging points of a group.
BUT I never could add charging points to a group or groups to charging point.
If I add charging points to a group the program runs through and I also have more charging points in the group, till I left the function.
If I try to add a group to a charging point I always get a duplicate-key error message.
Here my group entity.
#Entity
#Table(name = "tbl_cp_group")
public class CpGroupEntity {
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "tbl_cp_group_relation", joinColumns = { #JoinColumn(name = "cp_group_id") }, inverseJoinColumns = { #JoinColumn(name = "cp_id") })
private List<ChargingPointEntity> cps = new ArrayList<ChargingPointEntity>();
...
}
Here my charging point entity.
#Entity
#Table(name = "tbl_charging_point")
public class ChargingPointEntity {
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "tbl_cp_group_relation", joinColumns = { #JoinColumn(name = "cp_id") }, inverseJoinColumns = { #JoinColumn(name = "cp_group_id") })
private List<CpGroupEntity> groups = new ArrayList<CpGroupEntity>();
...
}
Here the code of my functions
#Transactional
public void administrateGroupCP(GroupDiff diff, long groupId, String username) throws BadInputRoutingException, NoSuchGroupException,
NotAuthorizedException, NoSuchChargingPointException {
try {
// Create add list
List<Long> addList = diff.getAdd();
// Get entities from database
CpGroupEntity groupEntity = cpGroupDAO.getGroup(groupId);
UserEntity userEntity = userDAO.getUser(username);
// Add charging points
addChargingPoints(addList, groupEntity, userEntity);
// Remove charging points
// removeChargingPoints(removeList, groupEntity, userEntity);
} catch (EntityNotFoundException e) {
throw new NoSuchGroupException();
}
}
#Transactional
private void addChargingPoints(List<Long> addList, CpGroupEntity groupEntity, UserEntity userEntity) throws NoSuchChargingPointException,
BadInputRoutingException {
...
// Add charging points to group
// 1. Clear list of charging points. Remove charging points which are to ignore from the charging point list with the add items
addEntitiesList.removeAll(ignore);
if (0 < addEntitiesList.size()) {
// 2. Add charging points to charging point list of entity
groupEntity.getCps().addAll(addEntitiesList);
// 3. Save changes
cpGroupDAO.updateChargingPoints(groupEntity);
}
}
}
Here the function of the cpGroupDAO
public void updateChargingPoints(CpGroupEntity group) {
entityManager.merge(group);
entityManager.flush();
}
I don't know where my errors are. But so wrong can't it be, when I get the entities. I only can't remove or add entries to the lists of groups or charging points.
I hope someone can help me.
we found the solution of the Problem.
The problem was, that there were several attributes with joins. And one of this joins doesn't work because the join table was empty. We write a tool which fills this table with default values and now it works on a magic way.
We don't know how exactly this the problem solved but now it is gone and everything works. Probably one "nullable" statment was/is missing.
AND we all hate Hibernate ;-)
But one problem left. It is not possible to remove all entities from the add list. But this is an other problem ^^.
// Add charging points to group
// 1. Clear list of charging points. Remove charging points which are to ignore from the charging point list with the add items
addEntitiesList.removeAll(ignore);
if (0 < addEntitiesList.size()) {
// 2. Add charging points to charging point list of entity
groupEntity.getCps().addAll(addEntitiesList);
// 3. Save changes
cpGroupDAO.updateChargingPoints(groupEntity);
}
Thank you very much for your help!
I want the first to be generated:
#Id
#Column(name = "PRODUCT_ID", unique = true, nullable = false, precision = 12,
scale = 0)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PROD_GEN")
#BusinessKey
public Long getAId() {
return this.aId;
}
I want the bId to be initially exactly as the aId. One approach is to insert the entity, then get the aId generated by the DB (2nd query) and then update the entity, setting the bId to be equal to aId (3rd query). Is there a way to get the bId to get the same generated value as aId?
Note that afterwards, I want to be able to update bId from my gui.
If the solution is JPA, even better.
Choose your poison:
Option #1
you could annotate bId as org.hibernate.annotations.Generated and use a database trigger on insert (I'm assuming the nextval has already been assigned to AID so we'll assign the curval to BID):
CREATE OR REPLACE TRIGGER "MY_TRIGGER"
before insert on "MYENTITY"
for each row
begin
select "MYENTITY_SEQ".curval into :NEW.BID from dual;
end;
I'm not a big fan of triggers and things that happen behind the scene but this seems to be the easiest option (not the best one for portability though).
Option #2
Create a new entity, persist it, flush the entity manager to get the id assigned, set the aId on bId, merge the entity.
em.getTransaction().begin();
MyEntity e = new MyEntity();
...
em.persist(e);
em.flush();
e.setBId(e.getAId());
em.merge(e);
...
em.getTransaction().commit();
Ugly, but it works.
Option #3
Use callback annotations to set the bId in-memory (until it gets written to the database):
#PostPersist
#PostLoad
public void initialiazeBId() {
if (this.bId == null) {
this.bId = aId;
}
}
This should work if you don't need the id to be written on insert (but in that case, see Option #4).
Option #4
You could actually add some logic in the getter of bId instead of using callbacks:
public Long getBId() {
if (this.bId == null) {
return this.aId;
}
return this.bId;
}
Again, this will work if you don't need the id to be persisted in the database on insert.
If you use JPA, after inserting the new A the id should be set to the generated value, i tought (maybe it depends on which jpa provider you use), so no 2nd query needed. then set bld to ald value in your DAO?