I have two entities having many to many relation. Relation table is also mapped ad entity. It has id column with IDENTITY strategy.
#Entity
#Table(name = "Employee")
public class Employee {
// ...
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "Employee_Project",
joinColumns = { #JoinColumn(name = "employee_id") },
inverseJoinColumns = { #JoinColumn(name = "project_id") }
)
Set<Project> projects = new HashSet<>();
// standard constructor/getters/setters
}
#Entity
#Table(name = "Project")
public class Project {
// ...
#ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<>();
// standard constructors/getters/setters
}
#Table
public class EmployeeProject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EmployeeProjectId", nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "EmployeeId", nullable = false)
private Employee employee;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "ProjectId", nullable = false)
private Project project;
}
If Employee entity doesn't have #JoinTable mapping generation SQL looks like this:
create table EmployeeProject (EmployeeProjectId integer generated by default as identity
However after adding #JoinTable mapping in Employee generation SQL changes to this:
create table EmployeeProject (EmployeeProjectId integer not null
How can I force hibernate to always generate first version of SQL?
You can't, and you also shouldn't. If you want to model the EmployeeProject entity, then you should also use it, i.e. map the many-to-many association as two one-to-many associations:
#Entity
#Table(name = "Employee")
public class Employee {
// ...
#ManyToMany(mappedBy = "employee", cascade = { CascadeType.ALL })
Set<EmployeeProject> projects = new HashSet<>();
// standard constructor/getters/setters
}
#Entity
#Table(name = "Project")
public class Project {
// ...
#ManyToMany(mappedBy = "project")
private Set<EmployeeProject> employees = new HashSet<>();
// standard constructors/getters/setters
}
Related
I have two entities Book and BookTag. They are connect by many to many relationship.
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idbooks", insertable = false, updatable = false)
private Long id;
// other columns
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "books_tags",
joinColumns = {#JoinColumn(name = "book_id")},
inverseJoinColumns = {#JoinColumn(name = "book_tag_id")}
)
Set<BookTag> tags = new HashSet<>();
// getters and setters
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "book_tag")
public class BookTag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", insertable = false, updatable = false)
private Long id;
// other columns
#ManyToMany(mappedBy = "tags")
private Set<Book> books = new HashSet<>();
// getters and setters
}
I try to add to the set of bookTags new BookTag entity using such service method
public void updateBook(Book book, String tag) {
if (tag != null && !tag.equals("")) {
BookTag bookTag = bookTagService.findBookTagByTagName(tag).orElse(new BookTag(tag));
Set<BookTag> allTags = book.getTags();
allTags.add(bookTag);
book.setTags(allTags);
}
bookRepository.save(book);
}
But it doesn't work, after I saved an entity into the mysql database it work correctly
view of books_tags table after inserting a new tag to the book
After I try to add new tag to the book using the method above I get such result
view of books_tags table after adding a new tag to the book
As you can see my method doesn't add new entity, it changes an existing entry for a new bookTag ID. Help me please solve this question.
This line could actually return an unsaved Book tag:
BookTag bookTag = bookTagService.findBookTagByTagName(tag).orElse(new BookTag(tag));
so call save on bookTag before adding it to the complete Book.
I created two simple entities for trying out the java persistence manytomany mapping. But whatever I try, the jointable won't be populated with a mapping and remains empty.
UserClass:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(targetEntity = Order.class ,fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "users_orders",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "order_id", referencedColumnName = "id")
)
#JsonIgnoreProperties(value = "orderUsers")
private Set<Order> userOrders = new HashSet<>();
}
OrderClass:
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy = "userOrders", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "userOrders")
private Set<User> orderUsers = new HashSet<>();
}
I added Getter/Setter/Constructor via Lombok.
Create and save an user. Create an order, add the user and save it. But still the jointable remains empty.
Any ideas?
I have the following table schema, where a simulation has many searches and any search has many properties.
Since I would like to persist a Simulation entity with its searches and their properties all at once, I mapped my entity like this:
Simulation.java
#Data
#EqualsAndHashCode(of = "id")
#ToString(exclude = "searches")
#Entity
#Table(name = "SIMULATION")
public class Simulation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "simulation_generator")
#SequenceGenerator(name = "simulation_generator", sequenceName = "SIMULATION_SEQ", allocationSize = 1)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "SIMULATION_ID")
private Set<SimulationSearch> searches = new HashSet<>(0);
// other fields
}
SimulationSearch.java
#Data
#EqualsAndHashCode(of = "id")
#ToString(exclude = "properties")
#Entity
#Table(name = "SIM_SEARCH")
public class SimulationSearch implements Serializable {
#EmbeddedId
private SimulationSearchId id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "SIMULATION_ID", referencedColumnName = "SIMULATION_ID"),
#JoinColumn(name = "POSITION", referencedColumnName = "POSITION")
})
private Set<SimulationSearchProperty> properties = new HashSet<>(0);
// other fields...
#Data
public static class SimulationSearchId implements Serializable {
#ManyToOne
#JoinColumn(name = "SIMULATION_ID", insertable = false, updatable = false)
private Simulation simulation;
private int position;
}
}
SimulationSearchProperties.java
#Data
#EqualsAndHashCode(of = "id")
#Entity
#Table(name = "SIM_SEARCH_PROPERTY")
public class SimulationSearchProperty implements Serializable {
#EmbeddedId
private SimulationSearchPropertyId id;
private String value;
#Data
public static class SimulationSearchPropertyId implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "SIMULATION_ID", referencedColumnName = "SIMULATION_ID", insertable = false, updatable = false),
#JoinColumn(name = "POSITION", referencedColumnName = "POSITION", insertable = false, updatable = false)
})
private SimulationSearch search;
private String label;
}
}
What happens is that Hibernate keeps printing the following query untill it goes on StackOverflowError.
select simulation0_.*, searches1_.*, properties5_.*
from simulation simulation0_
left outer join sim_search searches1_ on simulation0_.id = searches1_.simulation_id
left outer join sim_search_property properties5_ on searches1_.position = properties5_.position and searches1_.simulation_id = properties5_.simulation_id
where simulation0_.id = ?
While mapping between Simulation and SimulationSearch is very similar to SimulationSearch and SimulationSearchProperty mapping, this error started happening when I set ManyToOne annotation of SimulationSearch#properties as lazy fetch and didn't stop even if I set SimulationSearchPropertyId#search as lazy too.
What am I missing?
UPDATES
I'm using Hibernate 4.2.6.Final
Partial stacktrace log:
java.lang.StackOverflowError
at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:148)
at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:104)
at org.hibernate.engine.spi.QueryParameters.<init>(QueryParameters.java:81)
at org.hibernate.loader.Loader.loadEntity(Loader.java:2114)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:82)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:72)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3927)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:460)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:429)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:206)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:262)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1092)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1019)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)
at org.hibernate.type.EntityType.resolve(EntityType.java:490)
at org.hibernate.type.ComponentType.resolve(ComponentType.java:667)
at org.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:349)
at org.hibernate.type.ManyToOneType.hydrate(ManyToOneType.java:190)
at org.hibernate.type.ComponentType.hydrate(ComponentType.java:642)
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:775)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:708)
at org.hibernate.loader.Loader.processResultSet(Loader.java:943)
at org.hibernate.loader.Loader.doQuery(Loader.java:911)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:342)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:312)
at org.hibernate.loader.Loader.loadEntity(Loader.java:2121)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:82)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:72)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3927)
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:460)
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:429)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:206)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:262)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:150)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1092)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1019)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:672)
at org.hibernate.type.EntityType.resolve(EntityType.java:490)
at org.hibernate.type.ComponentType.resolve(ComponentType.java:667)
at org.hibernate.type.ComponentType.nullSafeGet(ComponentType.java:349)
at org.hibernate.type.ManyToOneType.hydrate(ManyToOneType.java:190)
at org.hibernate.type.ComponentType.hydrate(ComponentType.java:642)
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:775)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:708)
at org.hibernate.loader.Loader.processResultSet(Loader.java:943)
at org.hibernate.loader.Loader.doQuery(Loader.java:911)
...
I've just updated a bit the entity mapping, removing mappedBy annotation attribute and adding instead #JoinColumns annotations.
Now persistence is working fine, but the StackOverflowError is still there when I try to load a single simulation.
I've also cleaned up Hibernate generated sql removing uninteresting pieces of information.
You forgot to annotate you relationship to be bidirectional.
For your first class the change should be
#Data
#EqualsAndHashCode(of = "id")
#ToString(exclude = "searches")
#Entity
#Table(name = "SIMULATION")
public class Simulation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "simulation_generator")
#SequenceGenerator(name = "simulation_generator", sequenceName = "SIMULATION_SEQ", allocationSize = 1)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "SimulationSearchId")
private Set<SimulationSearch> searches = new HashSet<>(0);
// other fields
}
Without the attribute mappedBy a JPA provider assumes that you have 2 unidirectional relationship, which leads to an infinite circle reference between your data.
Reference
JSR 338: Java TM Persistence API, Version 2.1
11.1.40 OneToMany Annotation
4th paragraph, 2nd sentence
If the relationship is bidirectional, the
mappedBy element must be used to specify the relationship field or property of the entity that is the
owner of the relationship.
I'm trying to map up an existing database schema using Hibernate+JPA annotations.
One of my entities are mapped like this:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
private int department;
#Id
private int userId;
...
And another entity, Group:
#Entity
#Table(name = "groups")
public class Group implements Serializable {
#Id
private int department;
#Id
private int groupId;
...
Group and User should have a many-to-many relationship between them, but the issue is that the join table ("user_group") only has columns "DEPARTMENT, USERID, GROUPID" - i.e. the DEPARTMENT column needs to be used in both joinColumns and inverseJoinColumns:
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "user_groups",
joinColumns = { #JoinColumn(name = "department"), #JoinColumn(name = "groupid") },
inverseJoinColumns = {#JoinColumn(name = "department"), #JoinColumn(name = "userid") }
)
private List<User> groupUsers = new ArrayList<>();
which gives a mapping error - "Repeated column in mapping for entity".
However, it looks like this was/is possible using XML, because this exact example exists in the old Hibernate documentation. But I cannot find any evidence that this ever worked using annotations? I tried with #JoinFormula instead of #JoinColumn, but that does not compile. Is it possible?
Okay, I'm pretty sure it's not possible.
I found a promising workaround:
Create an #Embeddable for the "user_group" table:
#Embeddable
public class UserGroupMembership implements Serializable {
#ManyToOne
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "userid", name = "userid")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "department", value = "department"))
})
private User user;
public UserGroupMembership(User user) {
this.user = user;
}
public UserGroupMembership() {
}
public User getUser() {
return user;
}
}
The trick is that #ManyToOne allows you to use #JoinColumnsOrFormulas, so one of the join conditions can be a formula, which I doesn't seem to work for #ManyToMany (the #JoinColumnsOrFormulas annotation is ignored as it expects the join columns to be part of the #JoinTable annotation).
The UserGroupMemberships are then mapped as a ElementCollection:
#ElementCollection
#CollectionTable(name = "user_group", joinColumns = {
#JoinColumn(name = "department", referencedColumnName = "department"),
#JoinColumn(name = "groupid", referencedColumnName = "groupid")
})
#OrderColumn(name = "seq", nullable = false)
private List<UserGroupMemberships> groupUsers = new ArrayList<>();
This only works right now for a unidirectional many-to-many relationship.
i have an entity Entity1 that have one to many relation with Entity2 as follows:
1- Entity1:
#Entity
#Table(name = "Entity1", catalog = "mydb")
public class Entity1 implements java.io.Serializable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "entity1", cascade = javax.persistence.CascadeType.ALL)
#OrderBy("id")
private Set<Entity2> collection = new HashSet<Entity2>(
0);
}
2- Entity2: (equals and hashcode method overridden)
#Entity
#Table(name = "entity2", catalog = "advertisedb")
public class Entity2 implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pkid", unique = true, nullable = false)
#Basic(fetch = FetchType.EAGER)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "fk_entity1", nullable = false)
private Entity1 entity1;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "apid", nullable = false)
private Entity3 entity3;
}
3- Here's how i am removing the entity from the collection:
entity1Obj.getEntity2().remove(entity2);
log.debug("size after remove: "+ entity1Obj.getEntity2().size()); // size decreases correctly, so the entity is removed from the collection
entity1Dao.updateEntity1(entity1);
4- DAO method:
public void updateEntity1(Entity1 entity1) {
getCurrentSession().update(getCurrentSession().merge(entity1));
}
Problem: what i get in the console, is a select query for the entity2 that should be removed, and no delete query, and nothing is getting deleted.
please advise how to fix this issue.
i replaced cascade = CascadeType.ALL with orphanRemoval = true and it works fine now.