Spring Hibernate - saving a one to many association - spring

I would like to save a one to many relationship into the database. In this case one parent and two children.
DAO code
public void createMatch() {
UserEntity checker = new UserEntity();
UserEntity setter = new UserEntity();
setter.setChecker(checker);
checker.addSetter(setter);
if (checker != null)
sessionFactory.getCurrentSession().persist(checker);
}
The model code
public class UserEntity implements Serializable {
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="checker_id")
private UserEntity checker;
#OneToMany(mappedBy="checker", orphanRemoval=true, cascade = CascadeType.ALL)
private Set<UserEntity> setters = new HashSet<UserEntity>();
// getters and setters
public void addSetter(UserEntity setter) {
if(setters == null) {
setters = new HashSet<UserEntity>();
}
setter.setChecker(this);
this.setters.add(setter);
}
Jsp
Controller for post process
#RequestMapping(value="/student", method = RequestMethod.POST)
public ModelAndView hello(#ModelAttribute("checker") UserEntity checker) {
userService.createMatch();
return new ModelAndView ("redirect:/admin");}
The db it is saving to:
CREATE TABLE `user` (
`user_id` INT(11) NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(45) NULL DEFAULT NULL ,
`checker_id` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`user_id`),
FOREIGN KEY (`checker_id`) REFERENCES `user` (`user_id`));
The problem is that one one child is saved for the parent, and when it is saved all of the fields are shown to be null except for the userId. I would like to persist more than one object for a parent and have the values show up in the database. I would like to ask how I go about implementing it?

Your hello() method takes a checker as argument, but ignores it completely. It calls createMatch(), without any argument, which saves an empty checker containing an empty setter. So the result is quite expected.
For the othe fields to be filled, you have to fill them.

Related

Unknown Column Error while attempting a many-to-one entity mapping

I followed this example, renaming Student to Journey, and renaming Library to Station. My interpretation of the error is, the table schema does not have a field called 'departure' while the java class representation does. I didn't expect this to be a problem because I followed the JPA naming convention mentioned in this answer. In the example's case, lib + b_id == LIB_B_ID . In my case, departure + station_id == departure_station_id (my database naming scheme uses lowercase).
Other notable differences to the example, I don't have a persistence.xml, for id I use GenerationType.IDENTITY not GenerationType.AUTO .
Error
java.sql.SQLException: Unknown column 'departure' in 'field list'
Schema script
CREATE TABLE `station` (
`station_id` bigint(20) NOT NULL AUTO_INCREMENT,
`locker_id` bigint(20) DEFAULT NULL,
`station_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`station_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2579 DEFAULT CHARSET=utf8
CREATE TABLE `journey` (
`journey_id` bigint(20) NOT NULL AUTO_INCREMENT,
`departure_station_id` bigint(20) DEFAULT NULL,
`destination_station_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`journey_id`),
KEY `station_id_departure` (`departure_station_id`),
KEY `station_id_destination` (`destination_station_id`),
CONSTRAINT `journey_ibfk_1` FOREIGN KEY (`destination_station_id`) REFERENCES `station` (`station_id`),
CONSTRAINT `journey_ibfk_2` FOREIGN KEY (`departure_station_id`) REFERENCES `station` (`station_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Journey
#Entity
public class Journey implements Serializable{//Serializable required by #JoinColumn
//Station
public Journey(Station departure, Station destination){
this.departure = departure;
this.destination = destination;
}
//List<Station>
public Journey(List<Station> journeyStationList){
departure = journeyStationList.get(0);//0 is first index
destination = journeyStationList.get(journeyStationList.size() - 1);//-1 because first index of List is 0
}
private Long journeyId;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getJourneyId(){
return journeyId;
}
public void setJourneyId(Long journeyId){
this.journeyId = journeyId;
}
#ManyToOne
private Station departure;
public Station getDeparture(){
return departure;
}
public void setDeparture(Station departure){
this.departure = departure;
}
#ManyToOne
private Station destination;
public Station getDestination(){
return destination;
}
public void setDestination(Station destination){
this.destination = destination;
}
}
Station
#Entity
public class Station implements Serializable{//Serializable required by #JoinColumn
public Station(){//default constructor needed for JPA query
}
public Station(String stationName){
setStationName(stationName);
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long stationId;
public Long getStationId(){
return stationId;
}
public void setStationId(Long stationId){
this.stationId = stationId;
}
private String stationName;
public String getStationName(){
return stationName;
}
public void setStationName(String stationName){
this.stationName = stationName;
}
}
In contrast to the two examples I cross referenced A and B, I noticed in this question the #ManyToOne annotation was above the getter not the field's declaration. Because my currently working annotations (#id and #GeneratedValue) was also above a getter it seemed like a plausible solution. After moving the annotation to the getter, the error went away.
The examples were not wrong, they were just using a different access strategy than what I had inadvertently implicitly set by placing the #id annotation above the getter. By placing my #id annotation above the getter I implicitly specified property-based access resulting in hibernate calling the getter and setter methods to access my attributes, hence why my #ManyToOne annotation had to be above the getter. The examples I was referencing placed the #id annotation above the class variable itself, implicitly specifying field-based access in which case you should placing the #ManyToOne annotation above the class variables work. Source

Why doesn't Mybatis map a simple ENUM correctly?

I'm not doing anything out of the ordinary from what I can tell. I have a spring boot application using mybatis:
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
I have an application.properties config for mybatis that is pretty simple:
## MyBatis ##
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-statement-timeout=30
My database table looks like this:
CREATE TABLE workspace_external_references (
id CHAR(36) PRIMARY KEY,
workspace_id CHAR(36) NOT NULL,
site VARCHAR(255) NOT NULL,
external_id VARCHAR(255) NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
updated_at DATETIME(6) NOT NULL DEFAULT NOW(6),
FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE
)
With just a single entry like this:
'a907c0af-216a-41e0-b16d-42107a7af05f', 'e99e4ab4-839e-405a-982b-08e00fbfb2d4', 'ABC', '6', '2020-06-09 00:19:20.135822', '2020-06-09 00:19:20.135822'
In my mapper file I'm doing a select of all references like this:
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
And the java object that this is supposed to map to looks like this:
public class WorkspaceExternalReference {
private UUID id;
private UUID workspaceId;
private Sites site;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public WorkspaceExternalReference(
final Sites site,
final UUID workspaceId,
final String externalId) {
this.site = site;
this.workspaceId = workspaceId;
this.externalId = externalId;
}
}
public enum Sites {
ABC, XYZ;
}
Sooooo why doesn't this work? I get this error back:
Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'id' from result set. Cause: java.lang.IllegalArgumentException: No enum constant com.acme.Sites.a907c0af-216a-41e0-b16d-42107a7af05f
When there is no default constructor, you need to let MyBatis know which columns to pass to the constructor explicitly (in most cases).
With annotations, it would look as follows.
You can use <resultMap> and <constructor> in XML mapper.
#ConstructorArgs({
#Arg(column = "site", javaType = Sites.class),
#Arg(column = "workspace_id", javaType = UUID.class),
#Arg(column = "external_id", javaType = String.class)
})
#Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(#Param("workspaceId") final UUID workspaceId);
Other columns (i.e. id, created_at, updated_at) will be auto-mapped via setters (if there are) or reflection.
Alternatively, you can just add the default (no-arg) constructor to the WorkspaceExternalReference class. Then all columns will be auto-mapped after the class is instantiated.
Note: To make it work, there needs to be a type handler registered for UUID, but you seem to have done it already (otherwise the parameter mapping wouldn't work).

How to query a database having a composite key, which accepts queries where parts of the composite key are null in Spring JPA?

I have a webapp that uses a database with a composite key. I need to create an API which will accept the parameters of the composite key wherein each paramenter can be null.
(for eg. If all the parameters of the composite key are set as null and query is done, then it should just return all rows in db)
I have used QueryByExampleExeccutor but it keeps throwing a null pointer exception.
Below is the model indicative of 1 row of the DB
public class Row
{
#EmbeddedId
private RowKey rowkey;
#Column
private String rowAttribute;
//getters and setters for rowkey and rowAttribute
}
Below is the model indicative of RowKey
public class RowKey
{
#Column(name = "rowBlock")
private String rowBlock;
#Column(name = "rowSection")
private String rowSection;
//getters and setters for above fields
}
I'm using QueryByExampleExecutor by extending my Repository(or DAO) with it as
public interface RowRepo extends CrudRepository<Row, RowKey>, QueryByExampleExecutor<Row> {
}
In my service layer I use the following statement to call repo methods to return a list of matched rows from DB
public List<Row> getRowRangeQuery(rowBlock,rowSection,rowAttribute)
{
Row row = new Row(new RowKey(rowBlock,rowSection),rowAttribute);
List<Row> Result = (List<Row>)getRowRepository().findAll(Example.of(row));
return Result;
}
I get an InvocationTargetException which when unrolled reveals a null pointer Exception at rowSection.

JPA not returning the records which contain empty columns in the EmbeddedId

JpaRepository's findAll() method does not return the rows, if any of the field in the composite key is null.
This is the entity class with the EmbeddedId JobVaccinationPK
/**
* ApplicationParam entity. #author MyEclipse Persistence Tools
*/
#Entity
#Table(name="job_vaccination",schema="cdcis")
#SuppressWarnings("serial")
public class JobVaccination implements java.io.Serializable {
// Fields
#Column(name="default_yn", length=1)
private String defaultYn;
#EmbeddedId
private JobVaccinationPK jobVaccinationPK;
public JobVaccination(){
}
//setters getters
}
This is the Embedded class
#Embeddable
#SuppressWarnings("serial")
public class JobVaccinationPK implements Serializable{
#ManyToOne
#MapsId("job_category_id")
#JoinColumn(name = "job_category_id", nullable=true)
private JobCategoryTypeMast jobCategoryMast;
#ManyToOne
#MapsId("vaccination_id")
#JoinColumn(name = "vaccination_id", nullable=true)
private VaccinationMast vaccinationMast;
#ManyToOne
#MapsId("screening_type_id")
#JoinColumn(name = "screening_type_id", nullable=true)
private ScreeningTypeMast screeningTypeMast;
//getters and setters
}
Service implementation class
#Override
public SearchResult<JobVaccinationDto> getJobVaccination(JobVaccinationDto dto)
throws VaccinationException {
List<JobVaccination> vaccDetails = jobVaccinationRepo.findAll();
if(vaccDetails == null) return null;
List<JobVaccinationDto> jobVaccinationDtos = new ArrayList<JobVaccinationDto>();
jobVaccinationDtos = convertToDto(vaccDetails);
return new SearchResult<>(jobVaccinationDtos.size(), jobVaccinationDtos);
}
Here am able to insert a null value for either jobCategoryId or screeningTypeId, just like below row. But when I'm trying to fetch the rows which have empty values, it returns null. I've tried to debug but I was not able to find the cause.
This is the generated hibernate query:
Hibernate:
select
jobvaccina0_.job_category_id as job_cate4_13_,
jobvaccina0_.screening_type_id as screenin2_13_,
jobvaccina0_.vaccination_id as vaccinat3_13_,
jobvaccina0_.default_yn as default_1_13_
from
cdcis.job_vaccination jobvaccina0_
Hibernate:
select
jobcategor0_.job_category_id as job_cate1_11_0_,
jobcategor0_.job_category_name as job_cate2_11_0_,
jobcategor0_.job_category_name_ar as job_cate3_11_0_,
jobcategor0_.screening_type_id as screenin4_11_0_
from
cdcis.job_category_mast jobcategor0_
where
jobcategor0_.job_category_id=?
Hibernate:
select
screeningt0_.screening_type_id as screenin1_21_0_,
screeningt0_.active_yn as active_y2_21_0_,
screeningt0_.mmpid_required_yn as mmpid_re3_21_0_,
screeningt0_.screening_type as screenin4_21_0_
from
cdcis.screening_type_mast screeningt0_
where
screeningt0_.screening_type_id=?
Hibernate:
select
vaccinatio0_.vaccination_id as vaccinat1_27_0_,
vaccinatio0_.vaccination_name as vaccinat2_27_0_,
vaccinatio0_.vaccination_name_ar as vaccinat3_27_0_
from
cdcis.vaccination_mast vaccinatio0_
where
vaccinatio0_.vaccination_id=?
Going with #Adam Michalik answer. As a work-around I've introduced a new primary key field in the table, as we can't handle a null in the composite key.
Composite IDs cannot contain null values in any of the fields. Since the SQL semantics of NULL is that NULL <> NULL, it cannot be determined that a primary key (1, 2, NULL) is equal to (1, 2, NULL).
NULL means "no value" in SQL and its interpretation is up to you on a case-by-case basis. That's why SQL and JPA do not want to make assumptions that NULL = NULL and that a primary key containing a NULL identifies a single entity only.
You may choose to use a synthetic, generated primary key instead of the composite business primary key to overcome that. Then, you'd always have a non-null, single-column PK and nullable foreign keys.
change the data type of particular row in entity from int to integer

Using oneToMany relation, but saving data in individual tables at different point of time

I am working on a Spring-MVC application which has 2 tables in database and 2 domain classes. Class Person has oneTOMany relation with class Notes. I would like to add Person and notes both in database. So I googled, to find out many MVC based examples for the same problem. However they seem to assume a few things :
Data is being added in a static manner by the developer, mostly through Static void main() or another class.
Data regarding all the classes which are related is added altogether, eg : Table A has oneToMany relation, so the code will add data for both the tables in one class or one jsp file.
Other frameworks like Spring-Security at play(This point is understood).
So basically, similar examples with different names and developers is what I found. My problem is :
I don't have static void main, don't intend to use it.
I am adding data through HTML page wrapped inside JSP page.
I or the user will first register through the register form, just login later and then add notes, so I am not adding data for both tables at same time. (I have to believe this is possible by Hibernate)
Error :
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.journaldev.spring.model.Person
org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:294)
org.hibernate.type.EntityType.getIdentifier(EntityType.java:537)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:311)
org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:321)
org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:294)
Person Model :
#Entity
#Table(name="person")
public class Person implements UserDetails{
private static final GrantedAuthority USER_AUTH = new SimpleGrantedAuthority("ROLE_USER");
#Id
#Column(name="id")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "person_seq_gen")
#SequenceGenerator(name = "person_seq_gen",sequenceName = "person_seq")
private int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "person1")
private Set<Notes> notes1;
public Set<Notes> getNotes1() {
return notes1;
}
public void setNotes1(Set<Notes> notes1) {
this.notes1 = notes1;
}
Notes model :
#Entity
#Table(name="note")
public class Notes {
#Id
#Column(name="noteid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "note_gen")
#SequenceGenerator(name = "note_gen",sequenceName = "note_seq")
private int noteId;
#ManyToOne
#JoinColumn(name = "id")
private Person person1;
public Person getPerson1() {
return person1;
}
public void setPerson1(Person person1) {
this.person1 = person1;
}
NotesDAOImpl :
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
session.save(notes);
}
SQL schema :
CREATE TABLE public.person (
id INTEGER NOT NULL,
firstname VARCHAR,
username VARCHAR,
password VARCHAR,
CONSTRAINT personid PRIMARY KEY (id)
);
CREATE TABLE public.note (
noteid INTEGER NOT NULL,
sectionid INTEGER,
canvasid INTEGER,
text VARCHAR,
notecolor VARCHAR,
noteheadline VARCHAR,
id INTEGER NOT NULL,
CONSTRAINT noteid PRIMARY KEY (noteid)
);
ALTER TABLE public.note ADD CONSTRAINT user_note_fk
FOREIGN KEY (id)
REFERENCES public.person (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE;
Btw, the id in addNote method is just me checking if SpringSecurity is actually sending userid, and has properly loggedin, debug purpose.
So, I am unable to add notes once user is logged in, what am I doing wrong? Or this is not possible with Hibernate. In that case, let me find a gun to shoot myself.. :P
Your code will try to save notes. But these notes will not be linked to any Person. You have to do below sequence of operation.
Find the logged in person or the person for which you want to save the notes.
Create notes object which will be in transient state.
Attach notes to the person.
If it is bidirectional relationaship, then person to notes.
Below is the code template.
#Transactional
#Repository
public class NotesDAOImpl implements NotesDAO{
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf){
this.sessionFactory = sf;
}
#Override
public void addNote(Notes notes, int id) {
Session session = this.sessionFactory.getCurrentSession();
Person person = getPerson(); // this method should get logged in person or the person for whom you want to save the notes.
if (person.getNotes() == null) {
Set<Note> notes = new HashSet<Note>();
person.setNotes(notes);
}
person.getNotes().add(note);
note.setPerson(person); // If bidirectional relationship.
session.update(person); // if update does not work, try merge();
}
Also make sure you have cascade type set to MERGE in person entity on notes field.
Note: Above code is just example from your code and may have some compilation error. please correct according to your requirement.

Resources