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

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

Related

Insert and update data automatically into SQL table from another table

I have 2 tables and I want to update the first table with data after that the second table will be updated automatically.
I'm a beginner in spring boot and I really need your help.
I could insert data from table 1 to table 2 but if I update some data from table 1 then table 2 coudn't be updated.
What can I do?
This is what I have done so far: The two entities of the tables and the service I worked with to insert data into table 2 from table 1.
Table 1:
#Entity
#Table(name = "formation")
public class Formation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String cursus;
private String groupeaction;
private String module;
private String formateur;
#Temporal(TemporalType.DATE)
private Date date;
private Long nbrappart;
private Long nbrabsent;
private Long hf;
private Long jf;
private Long nbrheures;
private Long tauxh;
private Long ristourneprevis;
private Long couthebergttc;
private Long coutpausecafttc;
Table 2:
#Entity
#Table(name = "tablef")
public class Tablef {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String cursus;
private Long nbrappart;
private Long Sumnbrheures;
private Long Sumjf;
private Long jhf;
private String groupeaction;
the service i used :
public Boolean InserIntoTableF(Tablef tf) {
Long id = ThreadLocalRandom.current().nextLong();
tf.setId(id);
jdbc.execute("insert into tablef (id,cursus,groupeaction
,nbrappart,sumnbrheures,sumjf,jhf)\r\n" +
"select id,cursus,groupeaction,nbrappart,sum(nbrheures),sum(jf)
,sum(jf)*nbrappart\r\n" +
" from formation \r\n" +
"group by cursus ;");
return true;
}
The controller :
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api")
#PreAuthorize("hasRole('ADMIN')")
public class FormationController {
#Autowired
private FormationService formationservice;
#Autowired
private FormationRepository formationrepository;
#GetMapping("/formations")
public List<Formation> GetAll() {
return formationrepository.findAll();
}
#GetMapping("/formations/{id}")
public ResponseEntity<Formation> getFormationById(#PathVariable(value = "id") Long formationId)
throws ResourceNotFoundException {
Formation formation = formationrepository.findById(formationId)
.orElseThrow(() -> new ResourceNotFoundException("Formation not found for this id :: " + formationId));
return ResponseEntity.ok().body(formation);
}
#PostMapping("/formations")
public Formation createFormation(#Valid #RequestBody Formation formation) {
return formationrepository.save(formation);
}
// this is how i update my entity formation (table 1)
#PutMapping("/formations/{id}")
public ResponseEntity<Formation> updateFormation(#PathVariable(value = "id") Long formationId,
#Valid #RequestBody Formation formationDetails) throws ResourceNotFoundException {
Formation formation = formationrepository.findById(formationId)
.orElseThrow(() -> new ResourceNotFoundException("Formation not found for this id :: " + formationId));
formation.setCursus(formationDetails.getCursus());
formation.setGroupeaction(formationDetails.getGroupeaction());
formation.setModule(formationDetails.getModule());
formation.setFormateur(formationDetails.getFormateur());
formation.setDate(formationDetails.getDate());
formation.setNbrappart(formationDetails.getNbrappart());
formation.setNbrabsent(formationDetails.getNbrabsent());
formation.setHf(formationDetails.getHf());
formation.setJf(formationDetails.getJf());
formation.setNbrheures(formationDetails.getNbrheures());
formation.setTauxh(formationDetails.getTauxh());
formation.setRistourneprevis(formationDetails.getRistourneprevis());
formation.setCouthebergttc(formationDetails.getCouthebergttc());
formation.setCoutpausecafttc(formationDetails.getCoutpausecafttc());
final Formation updatedFormation = formationrepository.save(formation);
return ResponseEntity.ok(updatedFormation);
}
#DeleteMapping("/formations/{id}")
public Map<String, Boolean> deleteFormation(#PathVariable(value = "id") Long formationId)
throws ResourceNotFoundException {
Formation formation = formationrepository.findById(formationId)
.orElseThrow(() -> new ResourceNotFoundException("Employee not found for this id :: " + formationId));
formationrepository.delete(formation);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
#PostMapping(value = "/fileupload")
public ResponseEntity<Formation> uploadFile(#ModelAttribute Formation formation) {
Boolean isFlag=formationservice.saveDataFromFile(formation.getFile());
if(isFlag) {
return new ResponseEntity<>(HttpStatus.OK);
}else
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
// here where i inser data from formation(table1) to tablef (table2)
#PostMapping(value = "/test")
public Boolean AddTf(Tablef tf) {
return formationservice.InserIntoTableF(tf);
}
}
If you use spring boot jpa to persist your data then you can have a look at JPA EntityListener and #PostPersist
#Entity
#EntityListeners(MyEntityListener.class)
public class MyEntity {
#Id
#GeneratedValue
private int id;
private String field;
public MyEntity() { }
}
The MyEntityListener impl
public class MyEntityListener {
#PostPersist
void onPostPersist(MyEntity myEntity) {
// save data to second table that needs an update on myEntity save
}
}

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);
}

Hibernate transaction and session with multiple save

Thanks, let me completely change it.
Using:
Spring Boot, Hibernate JPA
I have created a link table with a composite primary key across all 3 columns(event_attendee_link_program)
I used the JPA tools in STS IDE to generate Entities from my tables and it came up with the below code. I removed some of the columns to save space.
EventAttendee.java
#Entity
#Table(name="event_attendee")
#NamedQuery(name="EventAttendee.findAll", query="SELECT e FROM EventAttendee e")
public class EventAttendee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="attendee_id")
private long attendeeId;
//bi-directional many-to-one association to EventAttendeeLinkProgram
#OneToMany(mappedBy="eventAttendee")
private List<EventAttendeeLinkProgram> eventAttendeeLinkPrograms;
public List<EventAttendeeLinkProgram> getEventAttendeeLinkPrograms() {
return this.eventAttendeeLinkPrograms;
}
public void setEventAttendeeLinkPrograms(List<EventAttendeeLinkProgram> eventAttendeeLinkPrograms) {
this.eventAttendeeLinkPrograms = eventAttendeeLinkPrograms;
}
public EventAttendeeLinkProgram addEventAttendeeLinkProgram(EventAttendeeLinkProgram eventAttendeeLinkProgram) {
getEventAttendeeLinkPrograms().add(eventAttendeeLinkProgram);
eventAttendeeLinkProgram.setEventAttendee(this);
return eventAttendeeLinkProgram;
}
public EventAttendeeLinkProgram removeEventAttendeeLinkProgram(EventAttendeeLinkProgram eventAttendeeLinkProgram) {
getEventAttendeeLinkPrograms().remove(eventAttendeeLinkProgram);
eventAttendeeLinkProgram.setEventAttendee(null);
return eventAttendeeLinkProgram;
}
}
EventAttendeeLinkProgram.java
#Entity
#Table(name="event_attendee_link_program")
#NamedQuery(name="EventAttendeeLinkProgram.findAll", query="SELECT e FROM EventAttendeeLinkProgram e")
public class EventAttendeeLinkProgram implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private EventAttendeeLinkProgramPK id;
//bi-directional many-to-one association to EventAttendee
#ManyToOne
#JoinColumn(name="attendee_id", insertable=false, updatable=false)
private EventAttendee eventAttendee;
//bi-directional many-to-one association to EventOptionsAttendeeType
#ManyToOne
#JoinColumn(name="attendee_type_id", insertable=false, updatable=false)
private EventOptionsAttendeeType eventOptionsAttendeeType;
//bi-directional many-to-one association to EventProgram
#ManyToOne
#JoinColumn(name="program_id", insertable=false, updatable=false)
private EventProgram eventProgram;
public EventAttendeeLinkProgram() {
}
public EventAttendeeLinkProgramPK getId() {
return this.id;
}
public void setId(EventAttendeeLinkProgramPK id) {
this.id = id;
}
public EventAttendee getEventAttendee() {
return this.eventAttendee;
}
public void setEventAttendee(EventAttendee eventAttendee) {
this.eventAttendee = eventAttendee;
}
public EventOptionsAttendeeType getEventOptionsAttendeeType() {
return this.eventOptionsAttendeeType;
}
public void setEventOptionsAttendeeType(EventOptionsAttendeeType eventOptionsAttendeeType) {
this.eventOptionsAttendeeType = eventOptionsAttendeeType;
}
public EventProgram getEventProgram() {
return this.eventProgram;
}
public void setEventProgram(EventProgram eventProgram) {
this.eventProgram = eventProgram;
}
}
EventAttendeeLinkProgramPK.java
#Embeddable
public class EventAttendeeLinkProgramPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="attendee_id", insertable=false, updatable=false)
private int attendeeId;
#Column(name="attendee_type_id", insertable=false, updatable=false)
private int attendeeTypeId;
#Column(name="program_id", insertable=false, updatable=false)
private int programId;
public EventAttendeeLinkProgramPK() {
}
public int getAttendeeId() {
return this.attendeeId;
}
public void setAttendeeId(int attendeeId) {
this.attendeeId = attendeeId;
}
public int getAttendeeTypeId() {
return this.attendeeTypeId;
}
public void setAttendeeTypeId(int attendeeTypeId) {
this.attendeeTypeId = attendeeTypeId;
}
public int getProgramId() {
return this.programId;
}
public void setProgramId(int programId) {
this.programId = programId;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof EventAttendeeLinkProgramPK)) {
return false;
}
EventAttendeeLinkProgramPK castOther = (EventAttendeeLinkProgramPK)other;
return
(this.attendeeId == castOther.attendeeId)
&& (this.attendeeTypeId == castOther.attendeeTypeId)
&& (this.programId == castOther.programId);
}
public int hashCode() {
final int prime = 31;
int hash = 17;
hash = hash * prime + this.attendeeId;
hash = hash * prime + this.attendeeTypeId;
hash = hash * prime + this.programId;
return hash;
}
}
EventAttendeeServiceImpl.java
#Service
#Primary
public class EventAttendeeServiceImpl implements EventAttendeeService {
#Autowired
private EventAttendeeRepository eventAttendeeRepository;
#Autowired
private EventOptionsAttendeeTypeRepository eventOptionsAttendeeTypeRepository;
#Autowired
private EventProgramRepository eventProgramRepository;
#Override
#Transactional
public String addEventAttendee(EventAttendee eventAttendee) {
EventAttendeeLinkProgram ep = new EventAttendeeLinkProgram();
ep.setEventOptionsAttendeeType(eventOptionsAttendeeTypeRepository.findOne(2L));
ep.setEventProgram(eventProgramRepository.findOne(2L));
eventAttendee.setEventAttendeeLinkPrograms(new ArrayList<>());
eventAttendee.getEventAttendeeLinkPrograms().add(ep);
eventAttendeeRepository.save(eventAttendee);
return "";
}
With this in place, my code is not throwing any errors. It is saving the EventAttendee, but nothing is being saved to the EventAttendeeLinkProgram. Please Note: I am trying so save both EventAttendee and EventAttendeeLinkProgram entities. So I think hibernate should be smart enought to forst save EventAttendee and generating the Id for it, then use that Id to store in EventAttendeeLinkProgram.
Why don't you let spring do the heavy lifting:
First create a JPA repository in spring:
public interface UserRepository extends CrudRepository<User, Long>{
}
Then create your 2 entities with the relationship
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true, fetch = FetchType.EAGER)
private List<UserType> userTypes;
And :
#Entity
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
My test looks like this:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
public class UserRepositoryTest extends AbstractTest {
#Autowired
private UserRepository userRepository;
#Test
#Transactional
public void test1() throws SQLException {
showTables();
User user1 = makeUser("Greg");
userRepository.save(user1);
System.out.println(user1);
userRepository.save(makeUser("George"));
assertEquals(2, userRepository.count());
User user = userRepository.findOne(1l);
}
User makeUser(String name) {
User user = new User();
user.setName(name);
user.setUserTypes(new ArrayList<>());
user.getUserTypes().add(makeUserType("admin"));
user.getUserTypes().add(makeUserType("head chef"));
return user;
}
UserType makeUserType(String description) {
UserType userType = new UserType();
userType.setDescription(description);
return userType;
}
}
First of all, user save return the identifier directly
Long insertId = (Long) session.save(user);
Then you'd better call the rollback on the txtransaction itself instead of retrieving again the transaction from the session.
Finally, when using spring you should consider to let spring manage the transaction itself (container managed transaction)using #Transactional annotation instead of using user managed transaction. It's logical as you let spring manage the session for you (sessionFactory.getCurrentSession()) and both session and transaction should have the same scope (e.g. the unit of work).
Consider reading some literature on Session (e.g. JPA entityManager) and transaction management.

Using a drop-down list in a model driven action class in Struts2

I'm populating an <s:select> from database. The action class is model-driven.
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class TestAction extends ActionSupport implements Serializable, ValidationAware, Preparable, ModelDriven<Transporter>
{
#Autowired
private final transient SharableService sharableService=null;
private static final long serialVersionUID = 1L;
private Transporter transporter; //Getter and setter
private Long transporterId; //Getter and setter.
private List<Transporter> transporters; //Getter only.
#Action(value = "Test",
results = {
#Result(name=ActionSupport.SUCCESS, location="Test.jsp"),
#Result(name = ActionSupport.INPUT, location = "Test.jsp")},
interceptorRefs={#InterceptorRef(value="defaultStack", params={"validation.validateAnnotatedMethodOnly", "true", "validation.excludeMethods", "load"})})
public String load() throws Exception
{
return ActionSupport.SUCCESS;
}
#Validations(
requiredFields={#RequiredFieldValidator(fieldName="transporterId", type= ValidatorType.FIELD, key = "transporter.required")})
#Action(value = "testInsert",
results = {
#Result(name=ActionSupport.SUCCESS, location="Test.jsp", params={"namespace", "/admin_side", "actionName", "Test"}),
#Result(name = ActionSupport.INPUT, location = "Test.jsp")},
interceptorRefs={#InterceptorRef(value="defaultStack", params={"validation.validateAnnotatedMethodOnly", "true"})})
public String insert() {
System.out.println("Selected item in the drop box : "+transporterId);
return ActionSupport.SUCCESS;
}
#Override
public void prepare() throws Exception {
transporters=sharableService.getTransporterList();
}
#Override
public Transporter getModel() {
return transporter;
}
}
and the following is <s:select> :
<s:select id="transporterId"
name="transporterId"
list="transporters"
value="transporterId"
listKey="transporterId"
listValue="transporterName"
headerKey="" headerValue="Select"
listTitle="transporterName"/>
This works perfectly.
I need this <s:select> in another action class which implements ModelDriven<ZoneTable>.
The table structure is simple, transporter->zone_table->country->state->city. There exists a one-to-many relationship between these tables.
How can we have a model driven action class implementing ModelDrven<ZoneTable> in which Transporter can be mapped to <s:select>, something like?
#Namespace("/admin_side")
#ResultPath("/WEB-INF/content")
#ParentPackage(value="struts-default")
public final class ZoneAction extends ActionSupport implements Serializable, ValidationAware, Preparable, ModelDriven<ZoneTable>
{
#Autowired
private final transient ZoneService zoneService=null;
#Autowired
private final transient SharableService sharableService=null;
private ZoneTable entity=new ZoneTable(); //Getter and setter.
private Long transporterId; //Getter and setter.
private List<Transporter> transporters; //Getter only.
#Override
public ZoneTable getModel() {
return entity;
}
#Override
public void prepare() throws Exception {
transporters=sharableService.getTransporterList();
}
}
Doing like this doesn't work. It doesn't set the value of transporterId upon submission, since the action class is implementing ModelDriven<ZoneTable> and not ModelDriven<Transporter> like the first case.
Is this possible using the model driven approach?
EDIT:
ZoneTable.java
public class ZoneTable implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "zone_id", nullable = false)
private Long zoneId;
#Column(name = "zone_name", length = 45)
private String zoneName;
#JoinColumn(name = "transporter_id", referencedColumnName = "transporter_id")
#ManyToOne(fetch = FetchType.LAZY)
private Transporter transporterId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "zoneTable", fetch = FetchType.LAZY)
private Set<ZoneCharge> zoneChargeSet;
#OneToMany(mappedBy = "zoneId", fetch = FetchType.LAZY)
private Set<Country> countrySet;
//Getters and setters + constructors.
}
Zone.jsp
<s:form namespace="/admin_side" action="Zone" validate="true" id="dataForm" name="dataForm" cssClass="search_form general_form">
<s:label key="label.zone.name" for="zone"/>
<s:textfield id="zoneName" name="zoneName" cssClass="validate[required, maxSize[45], minSize[2]] text-input text"/>
<s:fielderror fieldName="zoneName"/>
<s:label key="label.transporter.name" for="transporterId"/>
<s:select id="transporterId" name="transporterId" list="transporters" value="transporterId" listKey="transporterId" listValue="transporterName" headerKey="" headerValue="Select" listTitle="transporterName"/>
<s:fielderror fieldName="transporterId"/>
<s:text name="label.submit"/>
<s:submit id="btnSubmit" name="btnSubmit" value="Submit" action="AddZone"/>
</s:form>
Since this post has already a lot of code, I'm not posting the action class ZoneAction.java here. In case, it is needed, it is available here.
You need a converter to convert transporterId to Transporter Object. It goes like this:
package com.converter;
public class TransporterConverter extends StrutsTypeConverter {
#Override
public Object convertFromString(Map map, String[] strings, Class type) {
String value = strings[0]; // The value of transporterId submitted from the jsp
if (value != null && value.length() > 0) {
try {
Long longVal = Long.valueOf(value);
//Integer intVal = Integer.valueOf(value);
if (type == Transporter.class) {
Transporter data = find_transporter_from_the_back_by_transporter_id_using_longVal;
return data;
}
} catch (Exception ex) {}
}
return null;
}
#Override
public String convertToString(Map map, Object o) {
if ((o instanceof Transporter)) {
Transporter data = (Transporter) o;
//return the id of the Transporter Object
}
return null;
}
}
The next thing to do is to map this class in a file called xwork-conversion.properties. This file must reside in your classpath i.e. in classes directory. Enter the following entries in xwork-conversion.properties
package_of_transporter_class.Transporter=com.converter.TransporterConverter
I have not tested it, but I think it should work.
If you need more information on how type converters work, follow this url.

Resources