I'm using spring-boot 2.3.3 and spring-data-jdbc 2.0.3 to model a relationship between to elements guitar and classType. A guitar has a classType.
My schema in H2 (also in MySQL) is this:
CREATE TABLE class_type (
id bigint NOT NULL AUTO_INCREMENT,
description varchar(50) NOT NULL
PRIMARY KEY (id)
);
CREATE TABLE guitars (
id bigint NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
description varchar(1000) NOT NULL,
classType bigint NOT NULL,
PRIMARY KEY (id),
UNIQUE (name),
FOREIGN KEY (classType) references class_type(id)
);
And I have these classes:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(value = "guitars")
public class Guitar {
#Id
long id;
String name;
String description;
#MappedCollection(idColumn = "id")
#Column(value = "classType")
ClassType classType;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(value = "class_type")
public class ClassType {
#Id
Long id;
String description;
}
My repository is like this:
#Repository
public interface GuitarRepository extends CrudRepository<Guitar, Long>, PagingAndSortingRepository<Guitar, Long> {}
When I invoke this test:
#SpringBootTest
public class GuitarOneManyTest {
#Autowired
GuitarRepository guitarRepository;
#Test
void findAllByName() {
System.out.println(guitarRepository.findAll());
}
}
This sentence appears with an incorrect LEFT OUTER JOIN
SELECT `guitars`.`id` AS `id`, `guitars`.`name` AS `name`, `guitars`.`description` AS `description`, `classType`.`id` AS `classType_id`, `classType`.`description` AS `classType_description` FROM `guitars` LEFT OUTER JOIN `class_type` AS `classType` ON `classType`.`id` = `guitars`.`id`
But I want the select to be like this:
SELECT (...) FROM `guitars` LEFT OUTER JOIN `class_type` AS `classType` ON `classType`.`id` = `guitars`.`classType`
Did I miss something?
This is not a one-to-many relationship. One Guitar would reference multiple ClassType instances.
Instead it seems to be intended as a many-to-one relationship: Many Guitar instances might reference the same ClassType. This makes ClassType a different aggregate from Guitar and therefore it must not be referenced by a direct java reference, but only by it's id.
See Spring Data JDBC, References, and Aggregates for a more detailed explanation how to model such a relationship with Spring Data JDBC.
Related
I need to map with JPA the following legacy DB table structure I cannot change.
It’s a one to many relationship between table ao_rda_acq (1) -> ao_rda_acq_righe (many) (purchase requisition -> purchase requisition rows)
Table (1)
create table ao_rda_acq
(
id_divisione varchar(4) not null,
esercizio smallint not null,
id_rda varchar(10) not null,
...
other fields
...
constraint pk_ao_rda_acq
primary key (id_divisione, esercizio, id_rda)
)
table many
create table ao_rda_acq_righe
(
id_divisione varchar(4) ,
esercizio smallint not null,
id_rda varchar(10) not null,
nr_riga integer not null,
...
other fields
...
constraint pk_ao_rda_righe
primary key (id_divisione, esercizio, id_rda, nr_riga),
constraint ao_rda_acq_righe_ao_rda_acq_id_divisione_esercizio_id_rda_fk
foreign key (id_divisione, esercizio, id_rda) references ao_rda_acq
)
The primary key of table ao_rda_acq side one of the relationship has 3 fields id_divisione, esercizio, id_rda. The primay key of the table side many of the relationship has the same 3 filed plus a 4th field nr_riga.
I tryed with this JPA approch using #IdClass annotation for composite primary keys
#Table(name="ao_rda_acq")
#Entity
#IdClass(RdaId.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Rda {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name="id_divisione", referencedColumnName = "id_divisione"),
#JoinColumn(name="esercizio", referencedColumnName = "esercizio"),
#JoinColumn(name="id_rda", referencedColumnName = "id_rda")
})
#OrderBy("nrRiga")
public List<RdaRiga> righe = new ArrayList<>();
//Additional fields
}
where
public class RdaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
}
The entity for the rows is
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#IdClass(RdaRigaId.class)
#Table(name = "ao_rda_acq_righe")
public class RdaRiga {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#Id
public Long nrRiga;
//More fields
}
where
public class RdaRigaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
Long nrRiga;
}
This code compiles but JPA at start-up complains with this message
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.DuplicateMappingException: Table [ao_rda_acq_righe] contains physical column name [id_divisione] referred to by multiple logical column names: [id_divisione], [idDivisione]
Is this approch the best one to map my unhappy DB structure? If so what did I miss?
When you use #Id without the #Column annotation, the name of the column is assumed to be the name of the annotated property.
Given that your DB column seems to be *id_divisione* you need to use also the annotation #Column(name = "id_divisione").
This also applies to the other properties annotated with #Id.
I'm working with spring-boot-starter-data-jpa. Should I use annotation #GeneratedValue on my entity id if my code working without it and generate PRIMARY KEY automatically in mysqldb?
When I run the test in the sqltable appears new row with an ID with the following AUTO_INCREMENT value, while passed every time id 0.
Entity
#Data
#Entity
#RequiredArgsConstructor
#NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class Person {
#Id
// #GeneratedValue(strategy = GenerationType.IDENTITY)// use or not - the same effect
private int id;
#NonNull
private String name;
#NonNull
private String surname;
}
Repository
public interface PersonRepository extends CrudRepository<Person, Integer> {
Person findByNameAndSurname(String name, String surname);
}
Testing
#RunWith(SpringRunner.class)
#SpringBootTest
public class SpringRestInventoryPersistenceTests {
#Autowired
private PersonRepository personRepository;
#Test
public void personPersist() {
Person person = new Person("John", "Smith");
assertTrue(person.getId() == 0);
personRepository.save(person);
assertTrue(person.getId() == 0);
Person person2 = personRepository.findByNameAndSurname("John", "Smith");
assertEquals(person.getName(), person2.getName());
}//test passed
mySql table
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`surname` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8
I was also facing the same issue , I have added sequence generator in DB say 'idGenerator' ,and then add
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="idGenerator")
#SequenceGenerator(name='dbname' , sequenceName="idGenerator") ,
This will take the values from sequence generator created in DB
I`ve understood the reason for this behavior. Without the #GeneratedValue annotation, ID is not generated automatically and ID with value 0 is always passed to the mysql database. In this case mysql generate ID value from AUTO_INCREMENT. This is default behavior.
To disable this behavior you can set next parameter:
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'
Then after second call test method personPersist() we get error. In this case we can`t generate ID in mysql DB without #GeneratedValue annotation.
I am working with Spring MVC + Hibernate ( Spring data jpa ).
Here I am not getting how to create a table in Hibernate for the following scenario.
I have two tables :
1) User Details
2) Branch
Now, For every user, there is a field for branch, and that value should be from Branch table.
I have knowledge of OneToOne in hibernate. But it inserts a new entry for users branch field in Branch table. What I want is that, when I save user details in User Table, branch details should be just a reference from Branch table for matching row.
Thank you in advance
Suppose your branches can be identified by their names:
UserDetails user = new UserDetails();
...
user.setBranch(branchRepository.findOneByName());
...
userDetailsRepository.save(user);
Having:
#Entity
public class UserDetails {
#Id
#GeneratedValue
Long id;
#ManyToOne
Branch branch;
...
}
#Entity
public class Branch {
#Id
#GeneratedValue
Long id;
...
}
public interface BranchRepository extends Repository<Branch, Long> {
...
}
public interface UserDetailsRepository extends Repository<UserDetails, Long> {
...
}
You can use the User-Branch relationship by controlling the association through its Foreign Key.
And inside the User class, you will specify the OneToOne mapping as follows:
#OneToOne
#JoinColumn(name="User_Branch_ID")
private Branch branch;
And this "User_Branch_ID" refers to the foreign key which you have created while creating the User database table as follows:
create table BRANCH (
branch_id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NOT NULL,
city VARCHAR(30) NOT NULL,
country VARCHAR(30) NOT NULL,
PRIMARY KEY (address_id)
);
create table USER (
user_id BIGINT NOT NULL AUTO_INCREMENT,
user_branch_id BIGINT NOT NULL,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
PRIMARY KEY (user_id),
CONSTRAINT user_branch FOREIGN KEY (user_branch_id) REFERENCES BRANCH ( branch_id)
);
step 1: create a view
create view v_BRANCH_USER as
select
a.branch_id, a.name , a.city, a.country
b.user_id,b.first_name, b.last_name
from BRANCH a, USER b
where a.branch_id = b.user_branch_id
step 2: create a pojo and mapping to hibernate as a table
#Entity
#Table(name = "v_BRANCH_USER")
public class VBranchUser
String userId;
....
}
step 3: You can query it as a table (Criteria, HQL ..)
I'm working on database design and Java implementation of some sports event model.
I have two entities: Game and Participant and undirectional OneToMany relationship between them. Game can has many Participant's.
In database it looks like:
Of course i have FK constraint on table event_participant:
CONSTRAINT `FK_par_eve_eve_id__eve_id`
FOREIGN KEY (`event_id`)
REFERENCES `event` (`id`));
In Java code i wrote this mapping (simplified):
#Entity
#Table(name = "event")
public class GameEntity extends AbstractPersistable<Long> implements Game {
#Nullable
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "game")
private List<ParticipantEntity> participants;
}
#Entity
#Table(name = "event_participant")
public class ParticipantEntity extends AbstractPersistable<Long> implements GameParticipant {
#ManyToOne
#JoinColumn(name="event_id", referencedColumnName = "id", insertable = true)
private GameEntity game;
}
Test:
TeamEntity teamHome = teamsDao.findOne(1L);
TeamEntity teamGuest = teamsDao.findOne(2L);
GameEntity newEntity = new GameEntity()
.addParticipant(teamHome, GameParticipant.Alignment.HOME)
.addParticipant(teamGuest, GameParticipant.Alignment.GUEST);
dao.save(newEntity);
public GameEntity addParticipant(TeamEntity team, GameParticipant.Alignment alignment) {
if (participants == null) {
participants = new ArrayList<ParticipantEntity>();
}
participants.add(ParticipantEntity.create(team, alignment));
return this;
}
public static ParticipantEntity create(TeamEntity team, Alignment alignment) {
ParticipantEntity object = new ParticipantEntity();
object.team = team;
object.alignment = alignment;
return object;
}
When i'm trying to save the new GameEntity object which contains just created Participant's objects i expect that Hibernate creates one new record in event table and related records in event_participant table.
But i got an exception:
Referential integrity constraint violation: "FK_PAR_EVE_EVE_ID__EVE_ID: PUBLIC.EVENT_PARTICIPANT FOREIGN KEY(EVENT_ID) REFERENCES PUBLIC.EVENT(ID) (0)"; SQL statement:
insert into event_participant (id, alignment, event_id, participant_id, participant_type) values (null, ?, ?, ?, ?)
This insert query contains NULL value for event_id field. I don't understand why.
When i removed DB constraint everything is ok and event_id column has the correct value.
All the boilerplate is done by Spring Data util classes.
I'm using JPA 2.0, mysql-connector-java 5.1.28, hibernate-entitymanager 4.0.1.Final, spring-data-jpa 1.3.4.RELEASE and H2 database for testing.
I got the same result using either H2 or MySQL.
Where i failed?
You didn't set the other side of this bi-directional association:
public GameEntity addParticipant(TeamEntity team, GameParticipant.Alignment alignment) {
ParticipantEntity pe = ParticipantEntity.create(team, alignment)
participants.add(pe);
pe.setGame(this);
return this;
}
The thing to note is you didn't set the foreign key field for the participant as in pe.setGame(this)
I would like to implement inheritance in Hibernate.
I created ObjectClass object:
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
private id;
}
and CodeTable object that inhertance Object class:
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
}
in the database
object table is:
CREATE TABLE `object` (
`id` bigint(11) NOT NULL auto_increment,
PRIMARY KEY (`id`),
)
code_table table is:
-
CREATE TABLE `code_table` (
`id` bigint(11) NOT NULL auto_increment,
`description` varchar(45) character set latin1 default NULL,
PRIMARY KEY (`id`),
KEY `FK_object` (`id`),
CONSTRAINT `FK_object` FOREIGN KEY (`id`) REFERENCES `object` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
)
I wrote the following code to retreive data from codeTable:
#SuppressWarnings( "unchecked" )
#Transactional( readOnly = true, propagation = Propagation.REQUIRED )
public Collection<CodeTable> findAll() {
Session session = getSessionFactory().getCurrentSession();
return
session.createCriteria( persistentClass
).setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY
).list();
}
I gets empty list although there is one record in codetable table.
When I write the following SQL in my database:
SELECT * FROM `code_table`
I get:
id= 1,
description = company.
What went wrong in my Hibernate definition? How can I retrieve the object?
EDITED:
My hibernate.cfg.xml file looks like this:
<hibernate-configuration>
<session-factory>
<mapping class="com.mycompany.model.CodeTable" />
<mapping class="com.mycompany.model.ObjectClass" />
</session-factory>
</hibernate-configuration>
Your mappings and table structure are (roughly) correct for a JOINED inheritance strategy and I cannot reproduce your problem.
I use the following mappings (which are basically the one you provided):
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
#Id #GeneratedValue
private Long id;
public ObjectClass() { }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
And
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
public CodeTable() { }
public String getDescription() { return description; }
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "CodeTable [getDescription()=" + getDescription() + ", getId()="
+ getId() + "]";
}
}
The following tables:
create table code_table (
description varchar(255),
id bigint not null,
primary key (id)
)
create table object (
id bigint not null,
primary key (id)
)
alter table code_table
add constraint id
foreign key (id)
references object
And the following parent/child records:
insert into object values (1);
insert into code_table(id, description) values (1, 'foo');
And running your criteria query:
session.createCriteria(CodeTable.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Returns:
CodeTable [getDescription()=foo, getId()=1]
Everything works as expected.
References
JPA 1.0 Specification
2.1.10 Inheritance Mapping Strategies
Hibernate Annotations Reference Guide
2.2.4. Mapping inheritance
How does your mapping looks like ?
Have you read this section in the Hibernate doc ?
Inheritance mapping in Hibernate
As you can read in the link I provided above, your mapping is not correct. You have to let Hibernate know that the code_table class inherits from the object class, and you 'll have to let Hibernate know how this link exists in the database.