I have two entities Train and Station. They have #OneToMany relation on Timetable entity. In Timetable entity I have composite key on Train and Station as #ManyToOne.
All classes have setters and getters
Train
#Entity
#Table(name = "train")
public class Train {
#Id
#GeneratedValue
private Long id;
#Column(name = "type")
private String type;
#OneToMany(mappedBy = "train",fetch = FetchType.EAGER)
#OrderBy("curentnumber ASC")
private List<Coach> coaches;
#OneToMany(mappedBy = "train",fetch = FetchType.LAZY)
#OrderBy("arrivalTime ASC")
private List<Timetable> timetable ;
...
}
Station
#Entity
#Table(name = "station")
public class Station {
#Id
#GeneratedValue
private Long id;
#Column(name = "name", length = 16, nullable = false)
private String name;
#OneToMany(mappedBy = "station", fetch = FetchType.LAZY)
private List<Timetable> timetable;
...
}
Timetable
#Entity
#IdClass(TimetableId.class)
#Table(name = "timetable")
public class Timetable implements Serializable, Comparable<Timetable>{
#Id
#ManyToOne
#JoinColumn(name = "train_id", referencedColumnName = "id")
private Train train;
#Id
#ManyToOne
#JoinColumn(name = "station_id", referencedColumnName = "id")
private Station station;
#Column(name = "arrivalTime")
private Timestamp arrivalTime;
#Column(name = "departureTime")
private Timestamp departureTime;
...
}
TimetableId
public class TimetableId implements Serializable{
Long train;
Long station;
public TimetableId() {
}
...
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TimetableId that = (TimetableId) o;
if (train != null ? !train.equals(that.train) : that.train != null) return false;
return station != null ? station.equals(that.station) : that.station == null;
}
#Override
public int hashCode() {
int result = train.hashCode();
result = 31 * result + station.hashCode();
return result;
}
}
I have service class too.
//Result timetable
Timetable addedTimetable = new Timetable();
addedTimetable.setTrain(new Train());
addedTimetable.getTrain().setId(Long.valueOf(trainid));
addedTimetable.setStation(new Station());
addedTimetable.getStation().setId(Long.valueOf(stationid));
try{
timetableRepository.create(addedTimetable);
} catch (Exception e) {
e.printStackTrace();
}
And some from GenericDAO
#Override
public void create(T entity) {
EntityManager manager = emf.createEntityManager();
try {
try {
manager.getTransaction().begin();
manager.persist(entity);
manager.getTransaction().commit();
}
finally {
if (manager.getTransaction().isActive())
manager.getTransaction().rollback();
}
} finally {
manager.close();
}
}
The problem is when I have #IdClass annotation and try to create new Timetable referenced on new Station() and new Train with existing id, then i get error for "detached entity passed to persist:entity.Station" in my dao level. But if i remove #IdClass annotation, then everything is good and Timetable creates correctly. I think that i make something wrong in TimetableId and annotations...
Please help me, i stack here.
Related
so I have an hibernate Entity called Appointment, in this entity I have a AppointNumber property which itself contains a number property which is a string.
When I persist my Appointment, I need the AppointmentNumber. I got it to work with #Embedded and #Embeddable the other day but this creates a join table Which I can't have.
I tried many other solutions to try and get it to work without join tables but I can't figure it out. (I get lots of ava.lang.IllegalStateException)
Can anyone help?
Thanks!
#Entity(name = "appointments")
public class Appointment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#OneToOne(mappedBy = "number")
#Fetch(value = FetchMode.SELECT)
private AppointmentNumber appointmentNumber;
Appointment entity
AppointmentNumber, used in Appointment but should not be an entity
public class AppointmentNumber {
#OneToOne
#JoinColumn(name = "appointmentNumber", unique = true, nullable = false)
private String number;
You could do like this:
#Entity(name = "appointments")
public class Appointment {
///....
#Convert(converter = AppointmentNumberConverter.class)
private AppointmentNumber appointmentNumber;
///....
}
#Converter
public class AppointmentNumberConverter implements
AttributeConverter<PersonName, String> {
#Override
public String convertToDatabaseColumn(AppointmentNumber appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
return appointmentNumber.getNumber();
}
#Override
public AppointmentNumber convertToEntityAttribute(String appointmentNumber) {
if (appointmentNumber == null) {
return null;
}
AppointmentNumber result = new AppointmentNumber();
result.setNumber(appointmentNumber);
return result;
}
}
Have a look at JPA Converter documentation.
maybe duplicate question but I couldn't fina a solution for my case which I think is pretty simple.
I have two tables like so :
And those are the related DTO Object :
First table
#Entity
#Table(name = "DA10003_REF_SIGNALEMENT")
public class RefSignalement {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Column(name = "LIBELLE")
public String libelle;
#Column(name = "CATEGORIE")
public String categorie;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "CD_SIGNALEMENT")
public List<RefMessage> refMessages;
}
Second table :
#Entity
#Table(name = "DA10004_REF_MESSAGE")
public class RefMessage {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Id
#Column(name = "DESTINATAIRE")
public String destinataires;
#Column(name = "MESSAGE")
public String message;
}
And the following query to get all the RefSignelement with the associated message :
List<RefSignalement> listRefSignalement = em.createQuery("SELECT p FROM RefSignalement p, RefMessage m", RefSignalement.class).getResultList();
Unfortunately it's returning an empty list, I have tried to change it with join fetch but nothing change.
Thank for the help
Remember that in JPQL you have to think in Objects, not relations. You want to fetch all 'RefSignalement' and eagerly fetch their 'refMessages' properties:
SELECT DISTINCT s FROM RefSignalement s JOIN FETCH s.refMessages
Here the "distinct" is only needed by JPA when assembling your resulting entities, but add unnecessary overhead to the SQL Query. If you have a Hibernate version >= 5.2.2 (I think), then there is a query hint you can use to avoid that:
List<RefSignalement> sigs = entityManager
.createQuery(
"select distinct s " +
"from RefSignalement s " +
"left join fetch s.refMessages ")
.setHint("hibernate.query.passDistinctThrough", false)
.getResultList();
Read more about it here.
a couple of things, RefMessage class is using composite primary key so i guess you need to use #IdClass or #EmbeddedId annotation. here I'm providing using
#IdClass
public class RefId implements Serializable {
private String codeSignalement;
private String destinataires;
// default constructor
public RefId() {
}
public RefId(String codeSignalement, String destinataires) {
this.codeSignalement = codeSignalement;
this.destinataires = destinataires;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RefId refId = (RefId) o;
return Objects.equals(codeSignalement, refId.codeSignalement) &&
Objects.equals(destinataires, refId.destinataires);
}
#Override
public int hashCode() {
return Objects.hash(codeSignalement, destinataires);
}
}
then you need to use like follows
#Entity
#Table(name = "DA10004_REF_MESSAGE")
#IdClass(RefId.class)
public class RefMessage {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Id
#Column(name = "DESTINATAIRE")
public String destinataires;
#Column(name = "MESSAGE")
public String message;
}
define your repository as follows:
public interface RefSignalementRepo extends
JpaRepository<RefSignalement, String> {
}
RefSignalement class defination as follows:
#Entity
#Table(name = "DA10003_REF_SIGNALEMENT")
public class RefSignalement {
#Id
#Column(name = "CD_SIGNALEMENT")
public String codeSignalement;
#Column(name = "LIBELLE")
public String libelle;
#Column(name = "CATEGORIE")
public String categorie;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "CD_SIGNALEMENT")
public List<RefMessage> refMessages;
}
very example app
#SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(App.class, args);
RefSignalementRepo repo = context.getBean(RefSignalementRepo.class);
RefSignalement obj = new RefSignalement();
obj.codeSignalement = "1";
obj = repo.save(obj);
obj.refMessages = new ArrayList<>();
RefMessage message = new RefMessage();
message.codeSignalement = "1";
message.destinataires = "2";
message.message = "custom message";
obj.refMessages.add(message);
obj = repo.save(obj);
List<RefSignalement> objs = repo.findAll();
System.out.println(objs.get(0).refMessages.size());
EntityManager em = context.getBean(EntityManager.class);
List<RefSignalement> listRefSignalement = em.createQuery("SELECT p FROM RefSignalement p, RefMessage m", RefSignalement.class).getResultList();
System.out.println(listRefSignalement.get(0).refMessages.size());
}
}
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;
}
}
I'm facing a difficulty in developing a server in Spring (+ Hibernate + JPA) for a project.
The structure of the server (the part of interest in this case) is composed of catalogs composed of products that can have some related feedbacks.
Here I share the 3 entities:
Catalog.java
#Entity
#Data
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "catalog_user_id", nullable = false)
private User user;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "catalog_product",
joinColumns = {#JoinColumn(name = "catalog_id")},
inverseJoinColumns = {#JoinColumn(name = "product_id")}
)
private List<Product> products;
public Catalog() {}
}
Product.java
#Entity
#Data
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, unique = true)
private String name;
private String description;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Category category;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_user_id", nullable = false)
private User user;
public Product() {}
}
Feedback.java
#Entity
#Data
#Table(name = "feedback")
public class Feedback {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "feedback_user_id", nullable = false)
private User user;
#Column(nullable = false, length = 1)
#MapKeyEnumerated(EnumType.ORDINAL)
private Rating rating;
private String text;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "product_id", nullable = false)
private Product product;
public Feedback() {}
}
The problem occurs when I try to delete some entities. What I want is:
when I delete a catalog also the catalog references in the "catalog_product" join table should be deleted (but the product linked with the catalog should not be deleted);
when I delete a product also the product references in the "catalog_product" join table and the feedbacks related to that product should be deleted;
when I delete a feedback nothing happens.
In the business layer I have this operations:
CatalogServiceImpl.java
#Service
public class CatalogServiceImpl implements CatalogService {
#Autowired
private CatalogDAO catalogDAO;
#Autowired
private ModelMapper mapper;
public CatalogDTO findById(Long id) {
Catalog catalog = catalogDAO.findById(id);
return mapper.map(catalog, CatalogDTO.class);
}
public CatalogDTO findByName(String name) {
Catalog catalog = catalogDAO.findByName(name);
return mapper.map(catalog, CatalogDTO.class);
}
public List<CatalogDTO> findByUserId(Long id) {
List<Catalog> catalogs = catalogDAO.findByUserId(id);
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public List<CatalogDTO> findAll() {
List<Catalog> catalogs = catalogDAO.findAll();
Type listCatalogsType = new TypeToken<List<CatalogDTO>>() {}.getType();
return mapper.map(catalogs, listCatalogsType);
}
public CatalogDTO createCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB = catalogDAO.save(catalog);
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public CatalogDTO updateCatalog(CatalogDTO catalogDTO) {
Catalog catalog = mapper.map(catalogDTO, Catalog.class);
Catalog catalogFromDB;
if(catalogDAO.exists(catalog.getId())) {
catalogFromDB = catalogDAO.save(catalog);
} else {
catalogFromDB = null;
}
return mapper.map(catalogFromDB, CatalogDTO.class);
}
public void deleteCatalog(Long id) {
Catalog catalog = catalogDAO.findById(id);
if(catalog != null) {
catalogDAO.delete(catalog.getId());
}
}
}
ProductServiceImpl.java
#Service
public class ProductServiceImpl implements ProductService {
#Autowired
private ProductDAO productDAO;
#Autowired
private ModelMapper mapper;
public ProductDTO findById(Long id) {
Product product = productDAO.findById(id);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByName(String name) {
Product product = productDAO.findByName(name);
return mapper.map(product, ProductDTO.class);
}
public ProductDTO findByCategory(Category category) {
Product product = productDAO.findByCategory(category);
return mapper.map(product, ProductDTO.class);
}
public List<ProductDTO> findByUserId(Long id) {
List<Product> products = productDAO.findByUserId(id);
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public List<ProductDTO> findAll() {
List<Product> products = productDAO.findAll();
Type listProductsType = new TypeToken<List<ProductDTO>>() {}.getType();
return mapper.map(products, listProductsType);
}
public ProductDTO createProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB = productDAO.save(product);
return mapper.map(productFromDB, ProductDTO.class);
}
public ProductDTO updateProduct(ProductDTO productDTO) {
Product product = mapper.map(productDTO, Product.class);
Product productFromDB;
if(productDAO.exists(product.getId())) {
System.out.println(product.toString());
productFromDB = productDAO.save(product);
} else {
productFromDB = null;
}
return mapper.map(productFromDB, ProductDTO.class);
}
public void deleteProduct(Long id) {
Product product = productDAO.findById(id);
if(product != null) {
productDAO.delete(product.getId());
}
}
}
Now, when I try performing the operations of deletion of catalog or product an error of constraint key fail is triggered. For example trying to delete a product which has a reference in the catalog_product join table:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`e01`.`catalog_product`, CONSTRAINT `FKdx5j7bcx77t7h0hjw6tvoxmp1` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`))
I don't understand if there's a way to set the relations between entities to make what I want in an automatic way with Spring, or if I have to remove records with reference manually before the deletion of the catalog/product.
Thanks a lot in advance to everyone!
Luca
I have a following error:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`spindledb`.`section`, CONSTRAINT `FK_ftoru9cp83n512p9is8x3vo53` FOREIGN KEY (`scenario_id`) REFERENCES `scenario` (`scenario_id`))
Here are my classes:
Scenario:
#Entity
#Table(name = "scenario")
public class Scenario {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "scenario_id")
private int id;
#Column(name = "title", nullable = false)
private String title;
#NotNull
#DateTimeFormat(pattern = "dd/MM/yyyy")
#Column(name = "creation_date", nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate creationDate;
#ManyToOne
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "id", nullable = false)
private User user;
#OneToMany(mappedBy = "scenario", orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Set<Plot> plotList = new HashSet<Plot>();
#OneToMany(mappedBy = "scenario", orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
private Set<Character> characterList = new HashSet<Character>();
#OneToMany(mappedBy = "scenario", cascade=CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#OrderBy("sequence ASC")
private Set<Section> sectionList = new HashSet<Section>();
Section:
#Entity
#Table(name = "section")
public class Section {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "section_id")
private int id;
#Size(min = 4, max = 50)
#NotNull
#Column(name = "name")
private String name;
#NotNull
#Column(name = "type")
private String type = SectionType.TEXT.getSectionType();
#Column(name = "visibility")
private boolean visibility;
#NotNull
#Column(name = "sequence")
private int sequence;
#ManyToOne (cascade=CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "scenario_id", nullable = false)
private Scenario scenario;
Controller:
#RequestMapping(value = { "/delete-{id}-scenario" }, method = RequestMethod.GET)
public String deleteScenario(#PathVariable int id) {
scenarioService.deleteScenarioById(id);
return "redirect:/home";
}
Scenario service:
#Service("scenarioService")
#Transactional
public class ScenarioServiceImpl implements ScenarioService {
#Autowired
private ScenarioDao dao;
#Override
public Scenario findById(int id) {
return dao.findById(id);
}
#Override
public void saveScenario(Scenario scenario) {
dao.saveScenario(scenario);
}
public void updateScenario(Scenario scenario) {
Scenario entity = dao.findById(scenario.getId());
if(entity!=null){
entity.setTitle(scenario.getTitle());
entity.setCreationDate(scenario.getCreationDate());
}
}
#Override
public void deleteScenarioById(int id) {
dao.deleteScenarioById(id);
}
Dao
#Repository("scenarioDao")
public class ScenarioDaoImpl extends AbstractDao<Integer, Scenario> implements ScenarioDao {
#Override
public Scenario findById(int id) {
return getByKey(id);
}
#Override
public void saveScenario(Scenario scenario) {
persist(scenario);
}
#Override
public void deleteScenarioById(int id) {
Query query = getSession().createSQLQuery("delete from scenario where id = :id");
query.setString("id", ""+id);
query.executeUpdate();
}
I understand that the problem is that there may be a Section that can not exist without scenario. Right now however section table in database is empty and I still can't remove Scenario. Thanks for advice
Deleting an entity via Query would bypass any Cascade settings you put via annotation.
I would suggest find the entity first by id, then delete the entity object:
Object scenario = session.load(Scenario.class, id);
if (scenario != null) {
session.delete(scenario);
}
use cascade=CascadeType.ALL with all #ManyToOne relations in class Scenario because if you are going to delete any Scenario from database it must not be referenced any where in data base.
the other way to delete is.
Serializable id = new Long(1); //your id
Object persistentInstance = session.load(Scenario.class, id);
if (persistentInstance != null) {
session.delete(persistentInstance);
}