I have a postgres database and I am trying to make a simple REST service with Spring Boot. I have a problem with jpa ManytoMany relationship.
Person Entity:
#Entity
#Table(name = "person", schema = "persons")
public class Person implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#Column
private Integer age;
#ManyToOne
#JoinColumn(name = "country_id", referencedColumnName = "id")
private Country countryOfBirth;
#ManyToMany
#JoinTable(
name="persons_countries_residence",
joinColumns=#JoinColumn(name="person_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="country_id", referencedColumnName="id"))
private List<Country> countriesOfResidence;
// getters and setters and to String method overriden
}
Country Entity:
#Entity
#Table(name = "country", schema = "persons")
public class Country implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#Column(name = "country_name")
private String name;
#Column(name = "country_code")
private String code;
// getters and setters and to String method overriden
}
The postgres schema is the following:
Person Table:
CREATE TABLE persons.person
(
id serial NOT NULL,
name character varying(50) NOT NULL,
email character varying(40) NOT NULL,
age integer,
country_id serial NOT NULL,
CONSTRAINT id PRIMARY KEY (id),
CONSTRAINT country_id FOREIGN KEY (id)
REFERENCES persons.country (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Country table:
CREATE TABLE persons.country
(
id serial NOT NULL,
country_name character varying(45) NOT NULL,
country_code character varying(10) NOT NULL,
CONSTRAINT country_id PRIMARY KEY (id)
)
Join table:
CREATE TABLE persons.persons_countries_residence
(
person_id integer NOT NULL,
country_id integer NOT NULL,
CONSTRAINT person_country_id PRIMARY KEY (person_id, country_id),
CONSTRAINT persons_countries_residence_country_id_fkey FOREIGN KEY (country_id)
REFERENCES persons.country (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE NO ACTION,
CONSTRAINT persons_countries_residence_person_id_fkey FOREIGN KEY (person_id)
REFERENCES persons.person (id) MATCH SIMPLE
ON UPDATE CASCADE ON DELETE CASCADE
)
When i make an HTTP method call, I don't get the Countries of residence.
Service code:
#RequestMapping(method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public List<Person> getAllPersons() {
retutn jpaPersonRepository.findAll();
}
Any help appreciated.
Maybe, you need to specify a schema name in the join table name:
#JoinTable(
name="persons_countries_residence", schema="persons",
joinColumns=#JoinColumn(name="person_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="country_id", referencedColumnName="id"))
Update your Country class code like :
#Entity
#Table(name = "country", schema = "persons")
public class Country implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#Column(name = "country_name")
private String name;
#Column(name = "country_code")
private String code;
#ManyToMany(mappedBy = "countriesOfResidence")
private List<Person> persons;
// getters and setters and to String method overriden
}
Although a ManyToMany relationship is always bi-directional on the
database, the object model can choose if it will be mapped in both
directions, and in which direction it will be mapped in. If you choose
to map the relationship in both directions, then one direction must be
defined as the owner and the other must use the mappedBy attribute to
define its mapping. This also avoids having to duplicate the JoinTable
information in both places.
Do you mean that the country list is null? #ManyToMany associations are lazily loaded by default, you need to enable eager-fetching for it to work straight away.
#ManyToMany(fetch = FetchType.EAGER)
The solution is this:
#ManyToMany
#JoinTable(
name="persons_countries_residence", schema = "persons",
joinColumns=#JoinColumn(name="person_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="country_id", referencedColumnName="id"))
private List<Country> countriesOfResidence;
The schema had to be specified at the #JoinTable annotation as well.
Related
I want to join column without object reference. is that possible?
I want to do foreign key without object reference like that
#Data
#Entity
#Table(name = "HRM_EMPLOYEE_SALARY_INCREMENT")
public class EmployeeSalaryIncrement implements Serializable {
private static final long serialVersionUID = 9132875688068247271L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Integer id;
#Column(name = "REFERENCE_NO")
private String referenceNo;
#ManyToOne
#JoinColumn(name = "AUTHORITY", referencedColumnName = "id")
private Integer authority;
#ManyToOne
#JoinColumn(name = "PART_TWO_REGISTER_ID")
private Integer partTwoRegisterId;
#Column(name = "PART_TWO_ORDER_NO")
private String partTwoOrderNo;
#Column(name = "REMARKS")
private String remarks;
#Column(name = "HRM_TYPE")
private Integer hrmType;
}
If I found solve this problem, it will helpful for me.
Joining is not needed in this case. If you only need the foreign key value, then simply add the column as a #Column like any other:
#Data
#Entity
#Table(name = "HRM_EMPLOYEE_SALARY_INCREMENT")
public class EmployeeSalaryIncrement implements Serializable {
private static final long serialVersionUID = 9132875688068247271L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Integer id;
#Column(name = "AUTHORITY")
private Integer authority;
// other fields
// ...
}
No, I don't think that you can join columns between two entities without adding the reference of one to the related entity. You will have to create one entity class corresponding to each of your relational database table and add the reference of one to the other to establish relation.
However, I understand that you may not need all the attributes from your related table based upon your use case, and only wish to select one column from it. You can do that either by only adding required attributes in your joined table entity class (if you are sure you won't need other attributes for that table anywhere else).
Or you can use custom queries using JPQL in your repository class which selects only the required attributes from the tables that you have joined.
I will show you an example of the second way:
//Say, this is your entity class where you wish to join other table to fetch only one attribute from the joined table-
#Entity
#Table(name = "TABLE1", schema = "SCHEMA1")
public class Table1 {
#Id
#Column(name = "ID")
private String id;
#Column(name = "TABLE2_COLUMN")
private String table2Column;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "TABLE2_COLUMN1")
private Table2 table2; //refrence of the joined table entity object
}
// And this is the joined table entity class
#Entity
#Table(name = "TABLE2", schema = "SCHEMA1")
public class Table2 {
#Id
#Column(name = "ID")
private String id;
#Column(name = "TABLE2_COLUMN1")
private String table2Column1;
#Column(name = "TABLE2_COLUMN2")
private String table2Column2; // The column which we want to select from the joined table
}
In your repository class -
#Repository
public interface Table1Repository extends JpaRepository<Table1, String> {
#Query("SELECT t1 FROM Table1 t1 WHERE t1.id = :id")
public List<Table1> getTable1Rows(#Param("id") String id);
#Query("SELECT t1.table2.table2Column2 FROM Table1 t1 WHERE t1.id = :id")
public String getTable2Column2(#Param("id") String id);
}
Based upon the response from Markus Pscheidt below, I agree when he said there's no need to join the entities if you only need the attribute which is a foreign key. As foreign key is already present as an attribute in your entity (or table) you are working with.
If you need to fetch any other column apart from foreign key, then you may use JPQL to fetch the exact column that you wish to select.
Hi below is my schema definition
CREATE TABLE LOANS (
LOAN_ID NUMBER(9,0) PRIMARY KEY,
CORR_ID VARCHAR(5) NULL
);
CREATE TABLE DV_LOAN_PARTICIPANTS (
LOAN_ID NUMBER(9,0) ,
DVP_PARTICIPANT_NAME VARCHAR(50) NOT NULL,
DVP_PARTICIPANT_TYPE VARCHAR(50) NOT NULL,
PRIMARY KEY ("LOAN_ID", "DVP_PARTICIPANT_NAME")
);
LOANS Entity
#Table(name = "LOANS")
#Entity
public class Loans {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "LOAN_ID")
#MapKey(name = "dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
// getter and setters
}
DV_LOAN_PARTICIPANTS Entity
#Table(name = "DV_LOAN_PARTICIPANTS")
#Entity
public class DVLoanParticipants implements Serializable {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
// getters and setters
}
Service Class is
DVLoanParticipants dvLoanParticipants = new DVLoanParticipants();
dvLoanParticipants.setLoanId(Long.valueOf("196801758"));
dvLoanParticipants.setDvpParticipantName("VKP");
dvLoanParticipants.setDvpParticipantType("Developer");
Loans loanInsert = new Loans();
loanInsert.setLoanId(Long.valueOf("196801758"));
Map<String,DVLoanParticipants> partyMap = new HashMap<>();
partyMap.put("VKP",dvLoanParticipants);
loanInsert.setDvLoanParticipantsMap(partyMap);
repository.save(loanInsert);
But when i am executing the save i am getting error as
NULL not allowed for column "LOAN_ID"; SQL statement:
insert into dv_loan_participants (dvp_participant_type, loan_id, dvp_participant_name) values (?, ?,
?)
Git Hub Code
https://github.com/vinoykp/spring-jpa/tree/master/spring-boot-hibernate-crud-demo
I had the similar question
Why Value is not getting assigned in JPA for insert statement
What is the issue in association?
I am using Java8, SpringBoot 2.3.7 and JPA with Oracle
There is this legacy table (I cannot touch it, as it is in production with other applications)
TRANSFE (TRA_ID_PK NUMBER, ACCOUNT_ID NUMBER, .... )
CONSTRAINT "TRANSFE_UNIQUE" UNIQUE ("TRA_ID_PK")
In my new application some of the new Transfe will be TransfeIn, with only one field, so I created this table
TRANSFE_IN (ID NUMBER, ....)
CONSTRAINT "TRANSF_IN_PK" PRIMARY KEY ("ID")
Everything works fine with Jpa:
#Entity
#Table(name = "TRANSFE")
public class Transfe implements Serializable {
#Id
#Column(name = "TRA_ID_PK")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_TRANSFES")
#SequenceGenerator(name="SEQ_SW_TRANSFERENCIAS", sequenceName="SEQ_TRANSFES", allocationSize=1)
private Long id;
#ManyToOne
#JoinColumn(name = "TRA_ID")
private Account account;
#OneToOne(cascade = {CascadeType.ALL})
#PrimaryKeyJoinColumn
private TransfeIn transfeIn;
and
#Entity
#Table(name = "TRANSFE_IN")
public class TransferenciaEntrant {
#Id
private Long id;
#MapsId
#OneToOne(cascade = {CascadeType.ALL}, mappedBy = "transfeIn")
#JoinColumn(name = "id")
private Transfe transfe;
The problem raises when I add this to TRANSFE_IN
CONSTRAINT "TRANSFE_IN_FK" FOREIGN KEY ("ID") REFERENCES "TRANSFE" ("TRA_ID_PK")
So when there is a new TransfeIn to be stored with
accountRepository.save(account)
(Account has a #OneToMany(mappedBy = "account", cascade = CascadeType.ALL) List transfes), I get
SQLIntegrityConstraintViolationException: ORA-02291: integrity constraint (TRANSFE_ENTR_FK) violated - parent key not found
In the logs, I can see how a new TRANSFE_IN row with the right ID (taken from SEQ_TRANSFES) is being inserted. But the table TRANSFE is empty, yet.
What am I doing wrong? Should I not use the FK? Or there is something in the annotations to be changed?
I have two tables
CREATE TABLE `heroic_quality`
(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(515) NOT NULL UNIQUE,
PRIMARY KEY (`id`)
);
CREATE TABLE `hero`
(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(515) NOT NULL UNIQUE,
`quality_id` INT DEFAULT NULL,
FOREIGN KEY (`quality_id`) REFERENCES heroic_quality (id),
PRIMARY KEY (`id`)
);
And the objects in hibernate are
#Table(name = "heroic_quality")
#Entity(name = "heroic_quality")
public class HeroicQuality
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
#Fetch(FetchMode.SELECT)
private List<Hero> heroes;
//ommited getters and setters for shortness
}
#Table(name = "hero")
#Entity(name = "hero")
public class Hero
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
//ommited getters and setters for shortness
}
As you see my Hero class doesn't have reference to heroic quality, and I would like to keep it that way.
Also I have a repository
#Repository
public interface HeroicQualityDAO
extends PagingAndSortingRepository<HeroicQuality, Long>
{
Optional<HeroicQuality> findByName(String name);
List<HeroicQuality> findByOrderByIdDesc();
}
What I would like to do is have a method such as
Optional<HeroicQuality> findByHeroName(String heroName)
Such that if given a name of hero from Hero table I will be able to get heroic quality object.
How can I make such a method?
Is there any way I can get heroic quality object without having a reference to it in the hero object?
How can I go about doing that?
Add the following method to HeroicQualityDAO.
Optional<HeroicQuality> findByHeroesName(String heroName);
If you are not happy with the method name, you can do
#Query("Select h from HeroicQuality hq join hq.heros h where h.name = :name")
Optional<HeroicQuality> findByHeroName(String name);
I faced the issue described in the title saving my entity though everything in code and db tables seems ok.
Here is my code:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "company_id", referencedColumnName = "id")
List<CompanyObject> companyObjects;
}
#Entity
public class CompanyObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Enumerated(EnumType.STRING)
ObjectType type;
}
Here is my table definitions:
CREATE TABLE `company` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8
CREATE TABLE `company_object` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`type` varchar(50) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK__company` (`company_id`),
CONSTRAINT `FK__company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Object I'm trying to save contains the following info:
Company(id=32, companyObjects=[CompanyObject(id=null, type=NEW)])
Here is the code I use to save the object:
Controller method:
#RequestMapping(value = "/company/{id}", method = RequestMethod.POST)
public String editCompany(#PathVariable("id") long companyId,
#ModelAttribute("form") CompanyDto form) {
Company company = companyService.getCompanyById(companyId);
companyService.updateCompany(company, form);
return "redirect:/companies";
}
Service method:
#Transactional
public Company updateCompany(Company company, final CompanyDto form) {
company.getCompanyObjects().clear();
company.getCompanyObjects().addAll(form.getCompanyObjects());
return companyRepository.save(company);
}
Am I getting this right that hibernate automatically generate and populate all the missing ids in these objects? If yes what am I missing and why the error appears?
You have some problems here. Firstly, your table definitions are wrong, check your accreditation_object table, you have a foreign key there which references a column that doesn't exist: company_id should be accreditation_company_id.
(or is it just some copy-paste error?)
Secondly, your entities are wrong, try this way:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#OneToMany(mappedBy="company", cascade = CascadeType.ALL)
Set<CompanyObject> companyObjects;
}
#Entity
public class CompanyObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Enumerated(EnumType.STRING)
ObjectType type;
#ManyToOne
#JoinColumn(name = "company_id")
Company company;
}
Note the #ManyToOne annotation in the CompanyObject. If I understand correctly, you want to assign one or more CompanyObject to a Company, thus you have to have a #ManyToOne annotated Company typed field in the CompanyObject.
Now, if you want to save these objects, first save the Company instance, then iterate over the list of CompanyObjects, set the previously saved company instance, and then save the CompanyObjects, something like this:
Company company = new Company();
companyDao.persist(company);
List<CompanyObject> companyObjects = new ArrayList<>();
// populate the list somehow
// ...
for(CompanyObject obj: companyObjects){
obj.setCompany(company);
companyObjectDao.persist(obj);
}
Your updateCompany method is wrong, you should try something like the above code. (I cannot rewrite your example because it looks like something is missing there. What is CompanyDTO?)
Edit: You can use cascade saving (note: I've updated the Company entity), just be sure to set the Company instance to every CompanyObject instance, like:
#Transactional
public Company updateCompany(Company company, final CompanyDto form) {
company.getCompanyObjects().clear();
company.getCompanyObjects().addAll(form.getCompanyObjects());
for(CompanyObject obj : company.getCompanyObjects()){
obj.setCompany(company);
}
return companyRepository.save(company);
}
I think this should work, but I'm not a 100% sure. Give it a try.