I want to poulate ManyToMany table in my H2 database for test. I have Entities with many to many relationship. I know how to insert table "sessions" and table "speakers", but i dont know how to insert table "session_speakers":
#Entity
#Table(name = "sessions")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "session_id")
private Long sessionId;
#Column(name = "session_name")
private String sessionName;
#Column(name = "session_description")
private String sessionDescription;
#Column(name = "session_length")
private Integer sessionLength;
#ManyToMany
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn(name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id"))
private List<Speaker> speakers;
and
#Entity
#Table(name = "speakers")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Speaker {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "speaker_id")
private Long speakerId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "title")
private String title;
#Column(name = "company")
private String company;
#Column(name = "speaker_bio")
private String speakerBio;
#ManyToMany(mappedBy = "speakers")
#JsonIgnore
private List<Session> sessions;
My H2JpaConfig
#Configuration
#EnableTransactionManagement
public class H2JpaConfig {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
.setName("DataBaseTestConfig" + ZonedDateTime.now() + UUID.randomUUID()).build();
}
}
my application-h2.properties:
hibernate.connection.driver_class=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create\r\n
spring.jpa.database=h2
spring.datasource.username=test
spring.datasource.password=password
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql=trace
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.connection.url=jdbc\:h2\:mem\:testdb;;
spring.jpa.generate-ddl=true
and my data.sql for tests:
INSERT INTO sessions (session_name,session_length,session_description)
VALUES ('Keynote - The Golden Age of Software',45,''),
('A Better Way to Access Data with Spring Data',60,''),
('A Deep Dive Into Spring IoC',60,'') ...
INSERT INTO speakers (first_name,last_name,title,company,speaker_bio)
VALUES ('Sergio','Becker','Senior Developer','MicroOcean Software','Test'),
('James','Lowrey','Solutions Architect','Fabrikam Industries','Test') ...
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (1,40),
(2,4),
(3,5)...
when i run my tests i got error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #6 of URL [file:/C:/work/JIRA_work/nauka/spring_project/ps-spring-data-jpa/conference-demo/target/test-classes/data.sql]: INSERT INTO session_speakers (session_id,speaker_id) VALUES (1,40), (2,4), ... nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKBSD81C224TLAEPMSBQIWO3OBG: PUBLIC.SESSION_SPEAKERS FOREIGN KEY(SESSION_ID) REFERENCES PUBLIC.SESSIONS(SESSION_ID) (82)"; SQL statement:
INSERT INTO session_speakers (session_id,speaker_id) VALUES (1,40), (2,4) ...
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:603) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
at
I would like to know, how to properly populate ManyToMany table.
You are referencing non-existent IDs.
You should either insert IDs manually (not relying on them being generated by the database) in the first two tables and reference those later.
INSERT INTO sessions (speaker_id, session_name,session_length,session_description)
VALUES (1, 'Keynote - The Golden Age of Software',45,''),
(2, 'A Better Way to Access Data with Spring Data',60,''),
(3, 'A Deep Dive Into Spring IoC',60,'') ...
INSERT INTO speakers (session_id, first_name,last_name,title,company,speaker_bio)
VALUES (11, 'Sergio','Becker','Senior Developer','MicroOcean Software','Test'),
(12, 'James','Lowrey','Solutions Architect','Fabrikam Industries','Test') ...
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (1,11),
(2,12),
(3,11)...
Or you can populate the first two tables the way you do it now, but than you have to find the correct IDs when inserting into the junction table.
INSERT INTO session_speakers (session_id,speaker_id)
VALUES (
(SELECT session_id FROM sessions WHERE session_name = 'Keynote - The Golden Age of Software'),
(SELECT speaker_id FROM speakers WHERE last_name = 'Becker'))...
This way may not be the right one in your situation, but there are circumstances where it can be appropriate.
Related
I have a Postgres database containing Movies, Raters, and Ratings. Each Movie object and each Rater object has a OneToMany relationship with Rating. I'm using Spring Data JPA and have connected them with #JoinColumn annotations, and it all seemed to be working until I got to the actual logic that's supposed to use the information within the databases. I'm getting the following error when trying to create a new Rating object: ERROR 2772 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.stephenalexander.projects.movierecommender.rating.Rating.movie; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value
Here are my Rating and Movie classes:
#Entity(name = "Rating")
#Table(name = "rating")
public class Rating {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "rating_id")
private Long id;
#Column(name = "rating")
private Double ratingValue;
private LocalDateTime time;
#ManyToOne
#JoinColumn(name = "rater_id", nullable = false)
private Rater rater;
#ManyToOne
#JoinColumn(name = "movie_id", nullable = false)
private Movie movie;
public Rating(Integer movieID, Double ratingValue) {
this.ratingValue = ratingValue;
this.time = LocalDateTime.now();
}
#Entity(name = "Movie")
#Table(name = "movie")
public class Movie {
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
#Column(name = "movie_id")
private Integer id;
private String title;
private int year;
#Column(name = "posterurl")
private String posterUrl;
#Column(name = "runningtime")
private int runningTime;
#OneToMany(fetch = FetchType.LAZY, targetEntity = Rating.class, mappedBy = "movie")
List<Rating> ratings;
Here is the service layer for Ratings:
#Service
public class RatingService {
private final RatingRepository ratingRepository;
#Autowired
public RatingService(RatingRepository ratingRepository) {
this.ratingRepository = ratingRepository;
}
public void addNewRating(Rating rating) {
ratingRepository.save(rating);
}
And the RatingRepository has the (as far as I know) standard wiring for Spring Data JPA:
#Repository
public interface RatingRepository extends CrudRepository<Rating, Long> {
}
When I create a new rating, I am trying to do so by only passing the reference to the ID of the movie, letting the database connect them. I've been calling addNewRating(ratingObject) from the RatingService. Some advice I've received is to wire things up by storing references to IDs instead of having all of the objects hold other objects within them. Here, I had to add the private Movie in order to define the relationships between the entities, and I think this conflict is where my mental block currently lies.
As it is, it appears there's nothing interpreting the ID when I make a new Rating by giving an Integer reference in the constructor. Have I misconfigured the related columns, or is there an annotation I can add to the constructor within Rating to let it know that the Integer I'm giving when I make a new Rating is pointing to the private Movie within the class? Or do I need to wire my MovieRepository into my service layers anywhere I am creating a new rating in order to get an actual Movie object to pass into the Rating constructor? Thanks for your time!
EDIT: Added some clarification to the questions I'm asking.
I had a few issues, primary among them was what Lucia outlined in the comments. There was no reason to have the relationships from Rating to Movie and Rater be bidirectional, and that was my mistake having followed some tutorials.
I removed the Movie and Rater member variables entirely from Rating and went back to int and Long references to their IDs. The annotations in Movie and Rater now read:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "rating")
I also had an issue auto-generation of IDs and not letting the database assign an ID to Rater or Rating objects before trying to save them. All of this has really helped me get my head around the relationship between the database functionality and my view in Java, so thanks for your help everyone.
I have a problem while building a OneToMany relationship between two entities. I have a user who can have multiple Properties. I want to define this relationship and create tables in MySql however when I run it it gives me Exception in thread "main" java.lang.NullPointerException
at start.ApplicationStart.main(ApplicationStart.java:44)
and also there are 2 more columns in the user table in mysql "user_id" and city. Now what I defined so far in my code is:
in class user I have the following fields:
#Id
private String id;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL) //mapped by variable in class user
private List<Properties> properties;
and in class Properties I have:
#Column(name = "city")
private String city;
#Column(name = "address", nullable = false)
private String address;
#ManyToOne
#JoinColumn(name="user_id")// , nullable=false
private User user;
I don't understand why I have the extra columns in the user table. Also I am using entity manager for both tables. Any hint would be great
I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
I am using Spring mvc with hibernate and i have a lookup table in my SQL server database. this table has 4 columns
#Entity
#Table(name = "VLE_LOOKUP_DATA")
public class Lookup_Data{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long ID;
#Column(name = "ENTITYNAME")
private String ENTITYNAME;
#Column(name = "CODE")
private String CODE;
#Column(name = "DESCRIPTION")
private String DESCRIPTION;
}
This table has one to one relationship with multiple tables for example with student table.
#Entity
#Table(name = "STUDENT")
public class student{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long STUDENT_ID;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID")
private Lookup_Data DEPARTMENT_ID;
#Column(name = "Name")
private String Name;
#OneToOne
#JoinColumn(name = "GENDER_ID")
private Lookup_Data GENDER_ID;
}
Here the foreign key came from Lookup Table is not the Primary key(ID) of Lookup data.its column name CODE which is referenced as foreign key in other tables.That is why when i run this example the OneToOne relation gives error as
java.sql.SQLException: Conversion failed when converting the varchar to data type int.
because code is string value.
Is there any way to implement this scenario in Spring Mvc boot application?
Note: In sql server this query can do desired work.
SELECT * FROM STUDENT as e left join VLE_LOOKUP_DATA as di on e.DEPARTMENT_ID=di.CODE
JPA is designed to support wide range of DBMS ( database server ) so it just support SQL standard specification.
In your current case, compare equals between 2 fields is not supported by SQL spec ( may be just supported by sql server only ) so JPA raise this error. So I think instead of using relational mapping, to get the Lookup_data you can do normal JPQL or native query.
I am building a sample for ManyToMany relationship between: User(1) - ()AccessLevel() - (1)Role
I have implemented 3 classes in Java with hibernate implementation as follow:
Class User
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
#Column(name="USER_ID")
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Class Role
#Entity
#Table(name="role")
public class Role {
#Id
#GeneratedValue
#Column(name="role_id")
private Integer id;
#Column(name="role_name")
private String roleName;
Class AccessLevel
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Problem:
When I am persisting the User bean using merge method then an exception arise:
#Service
public class UserServiceImpl implements UserService {
#PersistenceContext
private EntityManager em;
#Override
#Transactional
public void save(User user) {
em.merge(user);
}
Exception
org.springframework.web.util.NestedServletException: Request process
ing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into access_level (user_id, role_id) values (?, ?)]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:894)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
As you can see hibernate is trying to run this query:
insert into access_level (user_id, role_id) values (?, ?)
From my point of view it seems like hibernate is not generating the primary key for AccessLevel even though I have added the #GeneratedValue to the id attribute.
Note:
I am working on production environment with Postgresql and evelopment environment with HSQL database that creates all schemas from the begining based on the entities description. Both environments generate same issue.
Regards,
Cristian Colorado
Reason:
It seems for ManyToMany relationships you do not need to define a class for the "Joining Table". Therefore if I eliminate AccessLevel entity the logic would work perfectly fine. I explain further:
Explanation:
When I defined the User class I also described the relationship with Role:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "access_level", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles = new HashSet<Role>(0);
Important thing here is I have told hibernate that User entity will relate to Role entity through a table known as "access_level" and such table will have user_id and role_id columns in order to join previous entities.
So far this is all hibernate needs in order to work the many to many relationship, therefore when mergin it uses that information to create and tun this script:
insert into access_level (user_id, role_id) values (?, ?)
Now, the problem cames when I defined a new entity for AccessLevel:
#Entity
#Table(name="access_level")
public class AccessLevel {
#Id
#GeneratedValue
private Integer id;
#Column(name="role_id")
private Integer roleId;
#Column(name="user_id")
private Integer userId;
Now I am telling hibernate that there is a table "access_level" related to AccessLevel entity and it has 3 columns, the most important would be Id which is primary key.
So I defined "access_level" twice!
Solution:
I eliminated the Entity for access_level table.
I re-write my production script in order to have "access_level" with
user_id/role_id columns only.
Note: It would be good to know how to add a primary key to the joining table without generating issues. An alternative would be adding a composed primary key in database(user_id/role_id) which would be independient from hibernate.
Why do you need a PK column in the join table? There will be a composite PK composed of user_id and role_id. Now, as you have discovered a JoinTable for #ManyToMany will only ever have two columns and at some point you may require additional data about this relationship.
e.g.
user_id
role_id
date_granted
You may then want to use your AccessLevel entity however you replace the #ManyToMany with #OneToMany from User to AccessLevel and optionally from Role > AccessLevel.
The Hibernate docs themselves advise against #ManyToMany:
Do not use exotic association mappings:
Practical test cases for real many-to-many associations are rare. Most
of the time you need additional information stored in the "link
table". In this case, it is much better to use two one-to-many
associations to an intermediate link class. In fact, most associations
are one-to-many and many-to-one. For this reason, you should proceed
cautiously when using any other association style.