Spring Data JPA - Method Name - #ManyToOne - java

I have an entity CpoPipeline with a relationship ManyToOne with CpoEnvironment:
#Entity
#Table(name = "cpo_pipeline", catalog = "cup_orchestrator")
public class CpoPipeline implements java.io.Serializable {
private String pipelineId;
private String pipelineName;
private CpoEnvironment cpoEnvironment;
#Column(name = "pipeline_id", unique = true, nullable = false)
public String getPipelineId() {
return this.pipelineId;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "environment_id", nullable = false)
public CpoEnvironment getCpoEnvironment() {
return this.cpoEnvironment;
}
//Getters and Setters
}
The entity CpoEnvironment:
#Entity
#Table(name = "cpo_environment", catalog = "cup_orchestrator")
public class CpoEnvironment implements java.io.Serializable {
private String environmentId;
private String environment;
private Set<CpoPipeline> cpoPipelines = new HashSet<CpoPipeline>(0);
#Id
#Column(name = "environment_id", unique = true, nullable = false)
public String getEnvironmentId() {
return this.environmentId;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "cpoEnvironment")
public Set<CpoPipeline> getCpoPipelines() {
return this.cpoPipelines;
}
//Getters and Setters
}
The repository for this entity with a method name:
#Repository
public interface PipelineRep extends JpaRepository<CpoPipeline, String> {
Optional<CpoPipeline> findByPipelineIdAndEnvironmentId(String pipelineId, String environmentId);
}
Error: Caused by: org.springframework.data.mapping.PropertyReferenceException: No property environmentId found for type CpoPipeline
How can I create a method name using one field from the entity and one field from the relation? Is it possible?

Yes possible, to use environmentId of CpoEnvironment entity use this way CpoEnvironmentEnvironmentId
Optional<CpoPipeline> findByPipelineIdAndCpoEnvironmentEnvironmentId(String pipelineId, String environmentId);

Related

"Provided id of the wrong type for" error when using save() method of an #Embeddable class

I am still working on my very first solo spring boot project. It is suppose to be a Rest API using the MariaDB example database Nation. There is the country_languages table which receives two foreign keys that also are the primary keys and has another regular field. First foreign key is the id from countries table and the second one is the id from languages table. When I use the save() method in order to create a new tuple I get this error:
org.springframework.dao.InvalidDataAccessApiUsageException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer; nested exception is java.lang.IllegalArgumentException: Provided id of the wrong type for class me.givo.nationdbapiproject.model.CountryLanguages. Expected: class me.givo.nationdbapiproject.model.CountryLanguagesId, got class java.lang.Integer
This is the country_languages table from the MariaDB example:
create table country_languages(
country_id int,
language_id int,
official boolean not null,
primary key (country_id, language_id),
foreign key(country_id)
references countries(country_id),
foreign key(language_id)
references languages(language_id)
);
I am using an #Embeddable class CountryLanguagesId in order to make a composite key as I found in this reference.
#Embeddable
public class CountryLanguagesId implements Serializable {
#Column(name = "country_id")
private Integer countryId;
#Column(name = "language_id")
private Integer languageId;
public CountryLanguagesId() {
}
public CountryLanguagesId(Integer countryId, Integer languageId) {
this.countryId = countryId;
this.languageId = languageId;
}
// + getters and setters
After that I created the entity for the country_languages table and its repository:
#Entity
#Table(name = "country_languages")
public class CountryLanguages {
#EmbeddedId
CountryLanguagesId countryLanguagesId = new CountryLanguagesId();
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("countryId")
#JoinColumn(name = "country_id")
private Countries countries;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#MapsId("languageId")
#JoinColumn(name = "language_id")
private Languages languages;
#Column(name = "official", length = 1, nullable = false)
private Integer official;
public CountryLanguages() {
}
public CountryLanguages(Countries country, Languages language, Integer official) {
this.countries = country;
this.languages = language;
this.official = official;
}
// + getters and setters
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, Integer> {
}
There are the countries and languages entities:
#Entity
#Table(name = "countries")
public class Countries {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id", length = 11, nullable = false)
private Integer countryId;
#Column(name = "name", length = 50, nullable = true)
private String name;
#Column(name = "area", nullable = false)
private BigDecimal area;
#Column(name = "national_day", nullable = true)
private java.sql.Date nationalDay;
#Column(name = "country_code2", length = 2, nullable = false)
private String countryCode2;
#Column(name = "country_code3", length = 3, nullable = false)
private String countryCode3;
#OneToMany(mappedBy = "countries", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Countries() {
}
public Countries(String name, BigDecimal area, Date nationalDay, String countryCode2, String countryCode3) {
this.name = name;
this.area = area;
this.nationalDay = nationalDay;
this.countryCode2 = countryCode2;
this.countryCode3 = countryCode3;
}
#Entity
#Table(name = "languages")
public class Languages {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", length = 11, nullable = false)
private Integer languageId;
#Column(name = "language", length = 50, nullable = false)
private String language;
#OneToMany(mappedBy = "languages", cascade = CascadeType.ALL)
private Set<CountryLanguages> countryLanguages;
public Languages() {
}
public Languages(String language) {
this.language = language;
}
public Integer getLanguageId() {
return languageId;
}
These are the entries I do when get the error:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class ICountryLanguagesJpaRepositoryTest {
#Autowired
private ICountriesJpaRepository countries;
#Autowired
private ILanguagesJpaRepository languages;
#Autowired
private ICountryLanguagesJpaRepository repository;
#Test
public void shouldSaveAndRemoveContinents() {
Countries patu = new Countries("Patu", new BigDecimal(67822.34), new Date(12321233232L), "PU", "PTU");
countries.save(patu);
Languages patuano = new Languages("Patuano");
languages.save(patuano);
CountryLanguages pLanguages = new CountryLanguages(patu, patuano, 0);
repository.save(pLanguages);
assertEquals(1, repository.findAll().size());
System.out.println(repository.findAll());
repository.deleteById(1);
assertEquals(0, repository.findAll().size());
}
I am doing this using a H2 database. Here is the complete debug console output. Sorry but cant paste it here due characters limitation.
Thanks!
Your repository definition is wrong. You should specify the embeddable type as primary key type:
#Repository
public interface ICountryLanguagesJpaRepository extends JpaRepository<CountryLanguages, CountryLanguagesId> {
}

Hibernate Map ID automatically from field

I have something similar to this:
#Entity
#Table(name = "claim", schema = "test")
public class Claim implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idClaim", unique = true, nullable = false)
private Integer idClaim;
#OneToOne(mappedBy = "claim", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private ClaimReturnInfo claimReturnInfo;
#Column(name = "notes")
private String notes;
// Getters and setters
}
#Entity
#Table(name = "claim_returninfo", schema = "test")
public class ClaimReturnInfo implements Serializable {
#Id
#Column(name = "Claim_idClaim")
private Integer id;
#MapsId("Claim_idClaim")
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Claim_idClaim")
#JsonBackReference
private Claim claim;
#Column(name = "description")
private String description;
// Getters and setters
}
ClaimReturnInfo Id is not autogenerated because we want to propagate the Id from its parent (Claim). We are not able to do this automatically and we are getting this error: ids for this class must be manually assigned before calling save() when 'cascade' is executed in ClaimReturnInfo .
Is it possible to map Claim Id into ClaimReturnInfo Id or should we do this manually?
Even if we set this ID manually on claimReturnInfo and we can perform updates, we still get this error when trying to create a new Claim:
// POST -> claimRepository.save() -> Error
{
"notes": "Some test notes on a new claim",
"claimReturnInfo": {
"description": "Test description for a new claimReturnInfo"
}
}
In the ServiceImplemetation:
#Override
#Transactional
public Claim save(Claim claim) throws Exception {
if(null != claim.getClaimReturnInfo()) {
claim.getClaimReturnInfo().setId(claim.getIdClaim());
}
Claim claimSaved = claimRepository.save(claim);
return claimSaved;
}
I have tried using the following mappings and from your comments it was apparent that Json object is populated correctly.
I have noticed that the annotation #MapsId is the culprit.If you check the documentation of #MapsId annotation it says
Blockquote
The name of the attribute within the composite key
* to which the relationship attribute corresponds. If not
* supplied, the relationship maps the entity's primary
* key
Blockquote
If you change #MapsId("Claim_idClaim") to #MapsId it will start persisting your entities.
import javax.persistence.*;
#Entity
#Table(name = "CLAIM")
public class Claim {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idClaim", unique = true, nullable = false)
private Long idClaim;
#Column(name = "notes")
private String notes;
#OneToOne(mappedBy = "claim", cascade = CascadeType.ALL, optional = false)
private ClaimReturnInfo claimReturnInfo;
public Long getIdClaim() {
return idClaim;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public ClaimReturnInfo getClaimReturnInfo() {
return claimReturnInfo;
}
public void setClaimReturnInfo(ClaimReturnInfo claimReturnInfo) {
if (claimReturnInfo == null) {
if (this.claimReturnInfo != null) {
this.claimReturnInfo.setClaim(null);
}
} else {
claimReturnInfo.setClaim(this);
}
this.claimReturnInfo = claimReturnInfo;
}
}
package com.hiber.hiberelations;
import javax.persistence.*;
#Entity
#Table(name = "CLAIM_RETURN_INFO")
public class ClaimReturnInfo {
#Id
#Column(name = "Claim_idClaim")
private Long childId;
#Column(name = "DESCRIPTION")
private String description;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Claim_idClaim")
private Claim claim;
public Long getChildId() {
return childId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Claim getClaim() {
return this.claim;
}
public void setClaim(Claim claim) {
this.claim = claim;
}
}

How to get fields from child table when parent table is queried in jpa

I have 2 tables question and question option. Question has a composite key. When I query question by an id how do i get question options as well. How can I ensure that I getting the question options as well. As of now I'm only getting the questions. Should I change the mapping or should I add some properties
Question
#Entity
#Getter
#Setter
#JsonIgnoreProperties({"assessment"})
public class Question implements Serializable {
#EmbeddedId
private QuestionAssessmentKey questionAssessmentKey;
public QuestionAssessmentKey getQuestionAssessmentKey() {
return questionAssessmentKey;
}
public void setQuestionAssessmentKey(QuestionAssessmentKey questionAssessmentKey) {
this.questionAssessmentKey = questionAssessmentKey;
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "assessmentId", insertable = false, updatable = false)
private Assessment assessment;
private String questionText;
private String questionURL;
private QuestionStatus questionStatus;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="assessmentId", referencedColumnName = "assessmentId"),
#JoinColumn(name="questionNumber", referencedColumnName = "questionNumber")
})
private List<QuestionOption> questionOptions;
public List<QuestionOption> getQuestionOptions() {
return questionOptions;
}
public void setQuestionOptions(List<QuestionOption> questionOptions) {
this.questionOptions = questionOptions;
}
public Assessment getAssessment() {
return assessment;
}
public void setAssessment(Assessment assessment) {
this.assessment = assessment;
}
// private int questionNumber;
private QuestionTypes questionType;
//Getters and setters
}
QuestionOptions
#Entity
public class QuestionOption {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int questionOptionId;
#ManyToOne
#JoinColumns({
#JoinColumn(name="assessmentId", referencedColumnName = "assessmentId"),
#JoinColumn(name="questionNumber", referencedColumnName = "questionNumber")
})
private Question question;
private Character questionOption;
//Getter and setter
}
QuestionAssessmentKey
#Embeddable
public class QuestionAssessmentKey implements Serializable {
private int questionNumber;
private String assessmentId;
}
AFAIK you cannot fetch all the data in a single query since it is a one-to-many relationship between questions and question options. However when you call getQuestionOptions on the fetched Question entity, it should load and return the corresponding set of options.

Hibernate callback for many-to-many join table operations?

Consider this scenario:
A Car can have a number of issues
An issue can be shared by a number of cars
I want to define this relationship using a Many-To-Many mapping with a join table.
And I want a generic column MODIFIED_TS to be maintained automatically on all three tables (yes - I can do it with database triggers - but that is not my ambition).
I have experimented with a variety of hibernate entity listeners and interceptors. They work fine - except for the join table.
Is there anyone out there who knows why callbacks like e.g. preInsert and onSave do not fire when rows are inserted or updated on the join table?
I am using:
Hibernate 4.3.10.Final
spring-data-jpa 1.8.1.RELEASE (does it matter?)
Base entity class:
#MappedSuperclass
public abstract class BaseEntity {
#Column(name = "MODIFIED_TS")
private Timestamp modifiedTs;
}
Car:
#Entity
#Table(name = "CAR")
public class Car extends BaseEntity {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "LICENSE_PLATE_NUMBER", nullable = false, length = 20)
private String licensePlateNumber;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "CAR_ISSUE", inverseJoinColumns = {#JoinColumn(name = "CAR_ID")},
joinColumns = {#JoinColumn(name = "ISSUE_ID")})
private Collection<Issue> issues;
}
Issue:
#Entity
#Table(name = "ISSUE")
public class Issue extends BaseEntity {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "TEXT", nullable = false, length = 2000)
private String text;
}
and CarIssue:
#Entity
#Table(name = "CAR_ISSUE")
public class CarIssue extends BaseEntity {
#Id
#Column(name = "ISSUE_ID", nullable = false)
private long issueId;
#Id
#Column(name = "CAR_ID", nullable = false)
private long carId;
}
My listener:
public class MyEntityListener implements PreUpdateEventListener, PreInsertEventListener {
public boolean setAudit(Object entity, String[] properties, Object[] state) {
if (entity instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) entity;
baseEntity.setModifiedTs(new Timestamp(new Date().getTime()));
List<String> propertiesList = Arrays.asList(properties);
state[propertiesList.indexOf("modifiedTs")] = baseEntity.getModifiedTs();
}
return false;
}
#Override
public boolean onPreInsert(PreInsertEvent event) {
return setAudit(event.getEntity(), event.getPersister().getPropertyNames(), event.getState());
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
return setAudit(event.getEntity(), event.getPersister().getPropertyNames(), event.getState());
}
}
and finally this piece of code:
Car car = new Car();
car.setLicensePlateNumber("ABC123");
ArrayList<Issue> issues = new ArrayList<>();
Issue issue = new Issue();
issue.setDescription("Two wheels missing");
issues.add(issue);
car.setIssues(issues);
carRepo.save(car);
This works fine - I get a row in all three tables - but MODIFIED_TS only gets populated for CAR and ISSUE.
Any help is greatly appreciated:-)

Hibernate generate old query after column renaming

I've renamed column in my database (from configuration_SYSTEM_ID to SYSTEM_ID), and added AttributeOverride annotation to my AbstractSubConfiguration class. But hibernate ignore this annotation and continue generate queries with old column names (configuration_SYSTEM_ID instead of SYSTEM_ID).
My classes looks like this:
AbstractSubConfiguration.java
#MappedSuperclass
#IdClass(Configuration.class)
public class AbstractSubConfiguration implements Dto, Serializable {
private static final long serialVersionUID = -6271877313478924753L;
#Id
#OneToOne(fetch = FetchType.LAZY)
#AttributeOverride(name = "systemId", column = #Column(name = "SYSTEM_ID"))
private Configuration configuration;
public AbstractSubConfiguration() {
super();
}
public AbstractSubConfiguration(final Configuration configuration) {
super();
this.configuration = configuration;
}
#XmlTransient
public final Configuration getConfiguration() {
return configuration;
}
public final void setConfiguration(final Configuration configuration) {
this.configuration = configuration;
}
}
Configuration.java
Entity(name = "CONFIGURATION")
public class Configuration implements Dto, Serializable {
private static final long serialVersionUID = 6601197795258837065L;
#EmbeddedId
private ConfigurationId configurationId;
#Column(name = "CONFIGURATION_NAME")
private String configurationName;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "configuration", fetch = FetchType.EAGER)
private BasicConfiguration basicConfiguration;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "configuration", fetch = FetchType.EAGER)
private AdvancedConfiguration advancedConfiguration;
.....
...
}
ConfigurationId.java
#Embeddable
public class ConfigurationId implements Serializable {
private static final long serialVersionUID = -5123943430808049180L;
private SystemId systemId;
#Column(name = "CONFIGURATION_ID")
private int configurationId;
public ConfigurationId(final SystemId systemId, final int configurationId) {
super();
this.systemId = systemId;
this.configurationId = configurationId;
}
....
....
}
And as a result I have an error 2695 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Unknown column 'tes0_.configuration_SYSTEM_ID' in 'field list'
From doc #AttributeOverride
java.lang.String name
(Required) The name of the property whose mapping is being overridden if property-based access is being used, or the name of the
field if field-based access is used.
In your mapping I don't see systemId property instead I see configurationId property
Edit
Accordance your editing name in #AttributeOverride should be configuration.systemId
See example from java doc with zipcode:
#Embeddable public class Address {
protected String street;
protected String city;
protected String state;
#Embedded protected Zipcode zipcode;
}
#Embeddable public class Zipcode {
protected String zip;
protected String plusFour;
}
#Entity public class Customer {
#Id protected Integer id;
protected String name;
#AttributeOverrides({
#AttributeOverride(name="state",
column=#Column(name="ADDR_STATE")),
#AttributeOverride(name="zipcode.zip",
column=#Column(name="ADDR_ZIP"))
})
#Embedded protected Address address;
...
}
Resolved it in this way:
#MappedSuperclass
#IdClass(Configuration.class)
public class AbstractSubConfiguration implements Dto, Serializable {
private static final long serialVersionUID = -6271877313478924753L;
#Id
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "SYSTEM_ID", referencedColumnName = "SYSTEM_ID"),
#JoinColumn(name = "configuration_CONFIGURATION_ID", referencedColumnName = "CONFIGURATION_ID")})
private Configuration configuration;
...
....
}

Categories