Constraint violation when reading data - spring-boot

I am writing integration tests with junit-jupiter and something very strange is happening -> Constraint violation exception occurs when I am reading (not saving the data)
storesTemplateRepository.findByCountryOrderByTemplateName(country, pageable); raises the following exception:
could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.STORES_TEMPLATE(ID)"; SQL statement:
insert into stores_template (country, stores, template_name, id) values (?, ?, ?, ?) [23505-196]]
Entity:
#Entity
#Getter
#Setter
#NoArgsConstructor
#Table(name = "STORES_TEMPLATE")
public class StoresTemplate {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "STORES_TEMPLATE_ID_SEQ")
#SequenceGenerator(name = "STORES_TEMPLATE_ID_SEQ", sequenceName = "STORES_TEMPLATE_ID_SEQ", allocationSize = 1)
private long id;
#Enumerated(EnumType.STRING)
private CountryEnum country;
private String templateName;
#Lob
private String stores;
public void setStores(List<String> stores) {
this.stores = String.join(",", stores);
}
#JsonIgnore
public List<String> getStoresAsList() {
return Arrays.stream(stores.split(","))
.distinct()
.collect(Collectors.toList());
}
}
Test
#Slf4j
#Transactional
#SpringBootTest
public class StoresTemplateControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private StoresTemplateRepository storesTemplateRepository;
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
public void fullApiTest() {
OAuth2AuthenticationToken dePrincipal = new TestDataBuilder()
.createOAuth2AuthenticationToken()
.setDefaultStoreUser()
.setRoles(SiamRoles.DE_CREATOR_ADMIN)
.build();
CreateStoresTemplateDto createStoresTemplateDeDto = CreateStoresTemplateDto.builder()
.country(CountryEnum.DE)
.stores(List.of("de1000", "de1100"))
.templateName("de template")
.build();
CreateStoresTemplateDto createStoresTemplateBgDto = CreateStoresTemplateDto.builder()
.country(CountryEnum.BG)
.stores(List.of("bg2000", "bg2100"))
.templateName("bg template")
.build();
CreateStoresTemplateDto createStoresTemplateDefaultDto = CreateStoresTemplateDto.builder()
.country(null)
.stores(List.of("de3000", "de3100"))
.templateName("default template")
.build();
try {
// find all existing by predefined insertion script
for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
storesTemplateRepository.deleteById(id);
}
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDeDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateBgDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE); // Here the exception occurs
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDefaultDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
}

JPA/Hibernate queues the operations in its session whenever possible, does not call the database instantly and then just before the transaction is completing, order those operations based on type and execute them. This is called Transactional write-behind in hibernate. As you can see, even though you called the delete first, hibernate will order it as last if it was queued.
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
So even though you do delete first as you can see hibernate will do it last. If you want to control the order, you need to flush it. So do the following.
for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
storesTemplateRepository.deleteById(id);
}
storesTemplateRepository.flush();
Reference
Executions Order

Related

Unable to insert rows in database using Spring Batch + Spring data

I am working with legacy database in which db tables has no keys. For java Sake i have to used Id annotation. My goal is to read data from .dat file and insert it into table. I'm using spring batch for the above said purpose. To improve the performance threading is used. But i'm getting insertion/updation issue that i'm unable to figure out. I have referenced many sources but none seems to solve my purpose. Kindly help me out by giving some appropriate solution or reference. Thanks in advance...
Entity.java
#Entity
#Table(name = "int_repl_mkt_val")
public class IntReplMktVal implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private IntReplMktValId id;
#Column(name = "acct_sys_cd")
private String acctSysCd;
#Column(name = "co_num", nullable = false)
private Integer coNum;
#Column(name = "last_mod_tmstmp")
#Temporal(TemporalType.TIMESTAMP)
private Date lastModTmstmp;
#Column(name = "pim_owned", nullable = false)
private String pimOwned;
#Column(name = "position", nullable = false)
private BigDecimal position;
#Column(name = "pricing_plan")
private String pricingPlan;
#Column(name="source_system",nullable=false)
private String sourceSystem;
... getter and setter
}
EmbeddedClass.java
#Embeddable
public class IntReplMktValId implements Serializable
{
private static final long serialVersionUID = 4824041485763129937L;
#Column(name = "acct_id",nullable=false)
private Integer acctId;
#Column(name = "asset_id",nullable=false)
private Integer assetId;
... getter and setter
}
jpaRepository.class
#Repository
public interface IntReplMktValRepository extends JpaRepository<IntReplMktVal, IntReplMktValId>
{
}
BatchConfiguration.class
#Configuration
public class IMAPPositionBatchConfiguration
{
#Autowired
JobBuilderFactory jobBuilderFactory;
#Autowired
StepBuilderFactory stepBuilderFactory;
#StepScope
#Bean(name="imapPositionReader")
public FlatFileItemReader<IMAPPositionInputMapperDTO> reader(#Value("#{jobParameters['fileName']}") String fileName) throws IOException
{
FlatFileItemReader<IMAPPositionInputMapperDTO> newBean = new FlatFileItemReader<>();
newBean.setName("fileReader");
newBean.setResource(new InputStreamResource(FileUtils.openInputStream(new File(fileName))));
newBean.setLineMapper(this.lineMapper());
newBean.setLinesToSkip(1);
return newBean;
}
public DefaultLineMapper<IMAPPositionInputMapperDTO> lineMapper()
{
DefaultLineMapper<IMAPPositionInputMapperDTO> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(this.lineTokenizer());
IMAPPositionReader imapPositionReader = new IMAPPositionReader();
lineMapper.setFieldSetMapper(imapPositionReader);
return lineMapper;
}
public DelimitedLineTokenizer lineTokenizer()
{
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter("|");
tokenizer.setNames("field1","field2","field3");
tokenizer.setIncludedFields(5,4,7);
return tokenizer;
}
public ItemProcessor<IMAPPositionInputMapperDTO, IntReplMktVal> processor()
{
return new IMAPPositionProcessor();
}
#Bean(name="imapPositionBatchWriter")
public ItemWriter<IntReplMktVal> writer()
{
return new IMAPPositionWriter();
}
#Bean(name="imapPositionListener")
public JobExecutionListenerSupport jobCompletionListener()
{
return new IMAPPositionJobListener();
}
#Bean(name="imapPositionTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50);
executor.setMaxPoolSize(100);
return executor;
}
#Bean(name="imapPositionStep")
public Step step(#Autowired #Qualifier("imapPositionTaskExecutor")TaskExecutor taskExecutor) throws IOException
{
return stepBuilderFactory.get("imapPositionStep")
.<IMAPPositionInputMapperDTO, IntReplMktVal>chunk(100)
.reader(this.reader(null))
.processor(this.processor())
.writer(this.writer())
.taskExecutor(taskExecutor)
.build();
}
#Bean(name="imapPositionFileImportJob")
public Job importUserJob(#Autowired #Qualifier("imapPositionStep") Step step)
{
return jobBuilderFactory
.get("imapPositionFileImportJob"+new Date())
.incrementer(new RunIdIncrementer())
.listener(this.jobCompletionListener())
.flow(step)
.end()
.build();
}
}
BatchWriter.java
public class IMAPPositionWriter implements ItemWriter<IntReplMktVal>
{
#Autowired
IntReplMktValRepository intReplMktValRepository;
#Override
public void write(List<? extends IntReplMktVal> items) throws Exception
{
intReplMktValRepository.saveAll(items);
}
}
ErrorLog
2019-06-07 17:22:01,522 ERROR [scopedTarget.imapPositionTaskExecutor-4] org.hibernate.internal.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.HibernateException: Duplicate identifier in table for: [com.capgroup.horizon.pricecapture.entities.IntReplMktVal#component[acctId,assetId]{assetId=274800, acctId=1}]]
NOTE: I have to insert all the data into table regardless of duplication or any other issue as keys are not defined so every data is valid.
Actually the problem was due to duplication found in persistent context which was resolved by setting the chunk size to 1.

How to show object's update history with Auditing?

I've got a problem, I made a CRUD in springboot with MYSQL and now I want to create a method which will return update history of my object...
I have class like:
#Entity
#Table
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"createdAt", "updatedAt"}, allowGetters = true)
#Audited
public class Note implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Getter
#Setter
private Long id;
#NotBlank
#Getter
#Setter
private String title;
#Version
#Getter
#Setter
private long version;
#NotBlank
#Getter
#Setter
private String content;
#Column(nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
#CreatedDate
#Getter
#Setter
private Date createdAt;
#Column(nullable = false)
#Temporal(TemporalType.TIMESTAMP)
#LastModifiedDate
#Getter
#Setter
private Date updatedAt;
}
But I don't know how can I now create a HTTP call to show that history of updates by #Audited.
I found something like this: Find max revision of each entity less than or equal to given revision with envers
But I don't know how to implement it in my project...
#RestController
#RequestMapping("/api")
public class NoteController
{
#Autowired
NoteRevisionService noteRevisionService;
#Autowired
NoteRepository noteRepository;
// Get All Notes
#GetMapping("/notes")
public List<Note> getAllNotes() {
return noteRepository.findAll();
}
// Create a new Note
#PostMapping("/notes")
public Note createNote(#Valid #RequestBody Note note) {
return noteRepository.save(note);
}
// Get a Single Note
#GetMapping("/notes/{id}")
public Note getNoteById(#PathVariable(value = "id") Long noteId) {
return noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
}
#GetMapping("/notes/{id}/version")
public List<?> getVersions(#PathVariable(value = "id") Long noteId)
{
return noteRevisionService.getNoteUpdates(noteId);
}
// Update a Note
#PutMapping("/notes/{id}")
public Note updateNote(#PathVariable(value = "id") Long noteId,
#Valid #RequestBody Note noteDetails) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
note.setTitle(noteDetails.getTitle());
note.setContent(noteDetails.getContent());
Note updatedNote = noteRepository.save(note);
return updatedNote;
}
// Delete a Note
#DeleteMapping("/notes/{id}")
public ResponseEntity<?> deleteNote(#PathVariable(value = "id") Long noteId) {
Note note = noteRepository.findById(noteId)
.orElseThrow(() -> new ResourceNotFoundException("Note", "id", noteId));
noteRepository.delete(note);
return ResponseEntity.ok().build();
}
}
getVersions its the call of function which Joe Doe sent me.
There: Repository
#Repository
public interface NoteRepository extends JpaRepository<Note, Long>
{
}
You can use AuditQuery for this. The getNoteUpdates method below returns a list of mappings. Each mapping contains an object state and the time of the update that led to that state.
#Service
#Transactional
public class NoteRevisionService {
private static final Logger logger = LoggerFactory.getLogger(NoteRevisionService.class);
#PersistenceContext
private EntityManager entityManager;
#SuppressWarnings("unchecked")
public List<Map.Entry<Note, Date>> getNoteUpdates(Long noteId) {
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(Note.class, false, false)
.add(AuditEntity.id().eq(noteId)) // if you remove this line, you'll get an update history of all Notes
.add(AuditEntity.revisionType().eq(RevisionType.MOD)); // we're only interested in MODifications
List<Object[]> revisions = (List<Object[]>) query.getResultList();
List<Map.Entry<Note, Date>> results = new ArrayList<>();
for (Object[] result : revisions) {
Note note = (Note) result[0];
DefaultRevisionEntity revisionEntity = (DefaultRevisionEntity) result[1];
logger.info("The content of the note updated at {} was {}", revisionEntity.getRevisionDate(), note.getContent());
results.add(new SimpleEntry<>(note, revisionEntity.getRevisionDate()));
}
return results;
}
}
Note that if you can restrict the query somehow (for example by filtering on a property), you should definitely do it, because otherwise performing the query can have a negative impact on the performance of your entire application (the size of the returned list might be huge if this object was often updated).
Since the class has been annotated with the #Service annotation, you can inject/autowire NoteRevisionService like any other regular Spring bean, particularly in a controller that handles a GET request and delegates to that service.
UPDATE
I didn't know that extra steps had to be taken to serialize a list of map entries. There may be a better solution but the following approach gets the job done and you can customize the format of the output revisionDate with a simple annotation.
You need to define another class, say NoteUpdatePair, like so:
public class NoteUpdatePair {
private Note note;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private Date revisionDate; // this field is of type java.util.Date (not java.sql.Date)
NoteUpdatePair() {}
public NoteUpdatePair(Note note, Date revisionDate) {
this.note = note;
this.revisionDate = revisionDate;
}
public Note getNote() {
return note;
}
public void setNote(Note note) {
this.note = note;
}
public Date getRevisionDate() {
return revisionDate;
}
public void setRevisionDate(Date revisionDate) {
this.revisionDate = revisionDate;
}
}
and now, instead of returning a list of map entries, you'll return a list of NodeUpdatePair objects:
#Service
#Transactional
public class NoteRevisionService {
private static final Logger logger = LoggerFactory.getLogger(NoteRevisionService.class);
#PersistenceContext
private EntityManager entityManager;
#SuppressWarnings("unchecked")
public List<NoteUpdatePair> getNoteUpdates(Long noteId) {
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(Note.class, false, false)
.add(AuditEntity.id().eq(noteId)) // if you remove this line, you'll get an update history of all Notes
.add(AuditEntity.revisionType().eq(RevisionType.MOD)); // we're only interested in MODifications
List<Object[]> revisions = (List<Object[]>) query.getResultList();
List<NoteUpdatePair> results = new ArrayList<>();
for (Object[] result : revisions) {
Note note = (Note) result[0];
DefaultRevisionEntity revisionEntity = (DefaultRevisionEntity) result[1];
logger.info("The content was {}, updated at {}", note.getContent(), revisionEntity.getRevisionDate());
results.add(new NoteUpdatePair(note, revisionEntity.getRevisionDate()));
}
return results;
}
}
Regarding your question about the service's usage, I can see that you've already autowired it into your controller, so all you need to do is expose an appropriate method in your NoteController:
#RestController
#RequestMapping("/api")
public class NoteController {
#Autowired
private NoteRevisionService revisionService;
/*
the rest of your code...
*/
#GetMapping("/notes/{noteId}/updates")
public List<NoteUpdatePair> getNoteUpdates(#PathVariable Long noteId) {
return revisionService.getNoteUpdates(noteId);
}
}
Now when you send a GET request to ~/api/notes/1/updates (assuming nodeId is valid), the output should be properly serialized.

Why is the child collection is null in One-To-Many relationship of spring boot application?

I create a spring boot application with MySQL,JPA,Web dependencies,and manually config my database settings in .properties file of Spring boot. I passed compiling, and started application successfully, and adding one record is normal fine.
BUT, i use method 'findAll(Pageable pageable)' i got a problem, that was
Could not write JSON: failed to lazily initialize a collection of roleļ¼Œcould not initialize proxy - no Session
I got confused, i started to debug my code, finally i found that the child collection of the result is null, and it contained an error, which is
"Exception occurred: com.sun.jdi.InvocationException occurred invoking method.."
I tried a lot to fix my code, but no use.
who can help me?
The entity relationship is a simple one to many:
TeacherInfo entity and ClassInfo entity, teacher manage multiple classes, just simple as this.
here is the enter point of my app:
#SpringBootApplication(exclude= {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceAutoConfiguration.class
})
#EnableTransactionManagement
public class OrmTestApplication {
public static void main(String[] args) {
SpringApplication.run(OrmTestApplication.class, args);
}
}
Database properties setting is here:
spring.datasource.primary.url=jdbc:mysql://localhost:3306/ormtest?useSSL=false
spring.datasource.primary.username=root
spring.datasource.primary.password=BlaNok2700
spring.datasource.primary.driver-class-name = com.mysql.jdbc.Driver
hibernate.hbm2ddl.auto = update
hibernate.show-sql = true
My Data base configure java code is here:
Configuration
#EnableJpaRepositories(basePackages = "com.lanjian.ormtest.repositories", entityManagerFactoryRef = "primaryEntityManagerFactory", transactionManagerRef = "primaryTransactionManager")
public class PrimaryDbConfig {
#Autowired
private Environment env;
#Bean
#ConfigurationProperties(prefix="spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource primaryDataSource() {
DataSourceProperties dbProperty = primaryDataSourceProperties();
return DataSourceBuilder.create()
.driverClassName(dbProperty.getDriverClassName())
.url(dbProperty.getUrl())
.username(dbProperty.getUsername())
.password(dbProperty.getPassword())
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(primaryDataSource());
factory.setPackagesToScan("com.lanjian.ormtest.entities");
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.show-sql", env.getProperty("hibernate.show-sql"));
factory.setJpaProperties(jpaProperties);
return factory;
}
#Bean
public PlatformTransactionManager primaryTransactionManager() {
EntityManagerFactory factory = primaryEntityManagerFactory().getObject();
return new JpaTransactionManager(factory);
}
}
My REST controller method is here:
#Autowired
private TeacherRepository teacherRepository;
#GetMapping("/page")
public Page<TeacherInfo> page(Pageable pageable){
Page<TeacherInfo> list = teacherRepository.findAll(pageable);
return list;
}
What happened
After i started my application, and use postman send request, i got this:
got a 500 error
And i debugger my code, found this:
child collection is null
In the picture, 'classes' is a list collection, but it is null, i don't understand.
Here are the TeacherInfo entity I defined
#Entity
#Table(name = "teacher")
public class TeacherInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private byte age;
private boolean male;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY, mappedBy="chargedTeacher")
private List<ClassInfo> classes = new ArrayList<>();
public void initialize() {
for (ClassInfo classInfo : classes) {
classInfo.setChargedTeacher(this);
for (StudentInfo studentInfo : classInfo.getStudents()) {
studentInfo.setClassInfo(classInfo);
}
}
}
//Setters and Getters}
Here is the ClassInfo Entity i defined
#Entity
#Table(name = "class_info")
public class ClassInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int capacity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "teacher_id",nullable=false)
#JsonIgnore
private TeacherInfo chargedTeacher;
#OneToMany(cascade = CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="classInfo")
private List<StudentInfo> students = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public TeacherInfo getChargedTeacher() {
return chargedTeacher;
}
public void setChargedTeacher(TeacherInfo chargedTeacher) {
this.chargedTeacher = chargedTeacher;
}
public List<StudentInfo> getStudents() {
return students;
}
public void setStudents(List<StudentInfo> students) {
this.students = students;
}
}
I think that the problem may come from Transactionality and JPA Fetching types.
Your repository method is being invoked not using a transaction, which implies that the transaction is on the boundaries of the method invocation (which might not be wrong). Spring returns a Page with objects but when it tries to serialize them, transaction is gone so no way to access childs.
I would suggest to put the JPA relationship as EAGER fetching, allowing all the objects to be present on the repository result when the transaction ends.
EDIT:
Answer to comments
#Bean
public PlatformTransactionManager primaryTransactionManager(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}

Spring/JPA: composite Key find returns empty elements [{}]

I have build my data model using JPA and am using Hibernate's EntityManager to access the data. I am using this configuration for other classes and have had no problems.
The issue is that I created an entity with a composite primary key (the two keys are foreign keys) , adding elements works perfectly I checked it in database but I am not able to retrieve the populated row from database.
For example if I query "FROM Referentiel" to return a list of all referentiels in the table, I get this [{},{}] my list.size() has the proper number of elements (2), but the elements are null.
The entity:
#Entity
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "id_projet")
private Projet projet;
#Id
#ManyToOne
#JoinColumn(name = "id_ressource")
private Ressource ressource;
#Column(name = "unite", nullable = false)
private String unite;
}
here is my controller getList method:
#PostMapping(value = "/list", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public List<Referentiel> listReferentiel(#RequestBody Long idProjet) {
List<Referentiel> referentiel = referentielService.listReferentiel(idProjet);
return referentiel;
}
and here is my dao methods:
#Autowired
private EntityManager em;
#Override
public void ajouterReferentiel(Referentiel ref) {
em.persist(ref);
em.flush();
}
#SuppressWarnings("unchecked")
#Override
public List<Referentiel> listReferentiel(Long idProjet) {
Query query = em.createQuery("Select r from Referentiel r where r.projet.idProjet=:arg1");
query.setParameter("arg1", idProjet);
em.flush();
List<Referentiel> resultList = query.getResultList();
return resultList;
}
Any help is greatly appreciated.
Try creating a class representing your composite key:
public class ReferentielId implements Serializable {
private static final long serialVersionUID = 0L;
private Long projet; // Same type than idProjet, same name than inside Referentiel
private Long ressource; // Same type than idRessource (I guess), same name than inside Referentiel
// Constructors, getters, setters...
}
And assign it to your entity having that composite key.
#Entity
#IdClass(ReferentielId.class) // <- here
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
// ...
}
Notice that it is required to have a class representing your composite keys, even if that does not help in your problem.

Hibernate Inheritance strategy=InheritanceType.JOINED & onetoMany with spring-data-jpa

For Some reason, I am not able to get the combination of Hibernate Inheritance strategy=InheritanceType.JOINED & onetoMany working. Following are the entities.
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="OBJECT_TYPE")
public abstract class ExamObject {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "examid", nullable = false)
private Exam exam;
}
#Entity
#DiscriminatorValue("Q")
public class ExamQuestion extends ExamObject{
private Integer questionNumber;
private String questionDesc;
}
#Entity
public class Exam {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer examid;
private String examName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set<ExamObject> object
}
My Spring Boot start up class
#SpringBootApplication
public class ExamApp implements CommandLineRunner {
#Autowired
private ExamQuestionRepository examQuestionRepository;
#Autowired
private ExamRepository examRepository;
public static void main(String[] args) {
SpringApplication.run(ExamApp.class, args);
}
#Override
#Transactional
public void run(String... arg0) throws Exception {
Exam exam = new Exam();
exam.setExamName("Exam1");
examRepository.save(exam);
String[] questions = new String[]{"Question1,Question2"};
ArrayList<ExamQuestion> examQuestions = new ArrayList<ExamQuestion();
int index = 0;
for(String questionNoDesc: questions){
index++;
ExamQuestion examQuestion = new ExamQuestion();
examQuestion.setQuestionDesc(questionNoDesc);
examQuestion.setQuestionNumber(index);
examQuestion.setExam(exam);
examQuestions.add(examQuestion);
}
examQuestionRepository.save(examQuestions);
Iterable<Exam> examGet = examRepository.findAll();
for (Exam exam2: examGet) {
System.out.println("Exam question is .. " +exam2.getObjects());
}
}
}
The problems is that whenever I print "Exam question is .. "+exam2.getObjects(), I always get null. How can I get this to work ?
As explained in the comment in the original question, the problem is that the object graph is not being maintained properly. One extra line of code to the following function fixed the issue. exam.setObjects(examQuestions);has been added
#Override
#Transactional
public void run(String... arg0) throws Exception {
Exam exam = new Exam();
exam.setExamName("Exam1");
examRepository.save(exam);
String[] questions = new String[]{"Question1,Question2"};
ArrayList<ExamQuestion> examQuestions = new ArrayList<ExamQuestion();
int index = 0;
for(String questionNoDesc: questions){
index++;
ExamQuestion examQuestion = new ExamQuestion();
examQuestion.setQuestionDesc(questionNoDesc);
examQuestion.setQuestionNumber(index);
examQuestion.setExam(exam);
examQuestions.add(examQuestion);
}
examQuestionRepository.save(examQuestions);
exam.setObjects(examQuestions);
Iterable<Exam> examGet = examRepository.findAll();
for (Exam exam2: examGet) {
System.out.println("Exam question is .. " +exam2.getObjects());
}
}
May be the issue is
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set object
When you fetch any thing by LAZY loading FetchType.LAZY. This will get all the object from teh parent table i.e Exam here but will not query the child/dependent tables for the data.
e.g Here it will not hit the ExamObject to get its data, it just replaces this by the proxy object, Thus if you query this object then you get null as the result.
Try your query with FetchType.EAGER

Resources