Many to many relationship doesn't add new Entity into the table - java

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.

Related

Hibernate changes way of id generation after using #JoinTable annotation

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
}

hibernate many-to-many association with extra columns in join table example

I have 3 tables as #Entity, and 2 join tables in my spring + hibernate app.
In one of join table i have extra column. I want to take info from this info column when i take info from my main table.
Main table code:
#Entity
#Table(name = "items")
public class Items {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "crafts"
,joinColumns = #JoinColumn(name = "item_id")
,inverseJoinColumns = #JoinColumn(name = "plot_id"))
private Set<Plots> plotInfo = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "item_materials"
,joinColumns = #JoinColumn(name = "item_id")
,inverseJoinColumns = #JoinColumn(name = "material_id"))
private Set<Materials> materialsInfo = new HashSet<>();
Table item_materials have this columns "id, item_id(fkey), material_id(fkey), expense" and one of this which names as "expense" i need to have in my final result.
How can i code my class to have "expense" in my result?
I read about #embeddable but still dont understand how to use in my project.
Don't use a #ManyToMany association. Map the join table as entity and model it similar to this:
#Entity
#Table(name = "items")
public class Items {
#OneToMany(mappedBy = "item")
private Set<Crafts> plotInfo = new HashSet<>();
}
#Entity
#Table(name = "plots")
public class Plots {
#OneToMany(mappedBy = "plot")
private Set<Crafts> items = new HashSet<>();
}
#Entity
#Table(name = "crafts")
public class Crafts {
#EmbeddedId
private CraftsId id;
#ManyToOne
#JoinColumn(name = "item_id", insertable = false, updatable = false)
private Items item;
#ManyToOne
#JoinColumn(name = "plot_id", insertable = false, updatable = false)
private Plots plot;
}
#Embeddable
public class CraftsId {
#Column(name = "item_id")
private Integer itemId;
#Column(name = "plot_id")
private Integer plotId;
// equals + hashCode
}

Why does Hibernate goes StackOverflowError when many-to-one is lazy?

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.

How to get rid of cyclic redundancy while having #ManyToMany relation JPA springboot

I am a newbie to the Spring boot (but worked in Laravel). I am facing a problem of cyclic redundancy in #ManyToMany relation. Let's go through the scenario -
What response I ma getting (fetching user's list which has many to many relationships with roles) -
Following is the ER-diagram of associated tables to manage many to many relationship between users and roles table.
User entity class has following code -
#Entity
#Where(clause = "deleted_at IS NULL")
#SQLDelete(sql = "UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "users")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class User {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name", nullable = false)
#NotBlank(message = "Name field can not be empty")
private String name;
.....
.....
.....
#ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
And Role entity is as follows -
#Entity
#Table(name = "roles")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#SQLDelete(sql = "UPDATE roles SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Where(clause = "deleted_at IS NULL")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "title")
#NotBlank(message = "Title field must be not null")
private String title;
......
......
......
#OneToMany(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> users;
}
How to solve this problem? What I am doing wrong here?
Since you are fetching the list directly. You will have to mention the annotation #JsonIgnore everywhere you have mapping specified. By everywhere I don't mean literally everywhere. Just use the annotation and see how it works.
Edit -> Just do it in roles table where you have mapped it to the user table. It will then skip the user mapping while fetching the data.
#JsonIgnore
private List<User> users;
You could annotate users within Role with #JsonBackReference.
Easiest would probably be to annotate the List<T>'s with a #JsonIgnoreProperties annotation to break the cyclic dependencies.
#JsonIgnoreProperties("users")
private List<Role> roles;
#JsonIgnoreProperties("roles")
private List<User> users;

Store List in table columns

I have table performance and hours. One performnace can be played many times at certain hours. One to Many relations.
#Entity
#Table(name = "performance_type")
#Data
public class PerformanceType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true)
private Integer performanceTypeId;
#Column(length=127)
private String performOptions;
}
and
#Entity
#Table(name = "performance")
#Data
public class Performance {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true)
private Integer performanceId;
#OneToMany(mappedBy = "performance")
private List<Hours> performanceHours = new ArrayList<>();
}
In database hibernate create table hours that have performanceId thas have only one value. How i may insert list values that performance with id 1,4,7 are played in the same time. That i need additoinal table hours_performance that store hourId and perfomanceId?
Since the relationship you want to achieve is (according to my understanding) many-to-many, you do indeed need a third table mapping hours to relationships. Here is a very good example. You will need to set up your third table with two foreign keys to the two tables you want to connect.
#Entity
#Table(name = "performance")
#Data
public class Performance {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true)
private Integer performanceId;
#ManyToMany
#JoinTable(
name = "hours_performance",
joinColumns = { #JoinColumn(name = "performance_id") },
inverseJoinColumns = { #JoinColumn(name = "hour_id") }
)
private List<Hours> performanceHours = new ArrayList<>();
}
#Entity
#Table(name = "hours")
#Data
public class Hours {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, unique = true)
private Integer hourId;
ZonedDateTime time;
#ManyToMany(mappedBy = "performanceHours")
private List<Performance> performances;
}

Categories

Resources