I have 2 tables Book and Author in a Many to Many Relationship using a candidate key table author_book which has primary keys of both author and Book.
I'm trying to get all books from a given author by AUTHOR_ID, maybe after if it works I'll try to do search by name
create table author_book(
`AUTHOR_ID` INT,
`BOOK_ID` INT
);
`
create table author(
`AUTHOR_ID` int auto_increment,
`FIRST_NAME` varchar(255),
`LAST_NAME` varchar(255),
`EMAIL` varchar(30),
`CONTACT_NO` VARCHAR(10),
primary key(AUTHOR_ID)
);
create table book(
`BOOK_ID` int auto_increment,
`TITLE` varchar(255),
`SUBJECT` varchar(255),
`PUBLISHED_YEAR` int,
`ISBN` varchar(30),
`QUANTITY` int,
`SHELF_DETAILS` varchar(255),
`PUBLISHER_ID` int,
`BOOK_COST` INT,
primary key(BOOK_ID)
);
I decided I'll create AUTHOR_NAME using CONCAT
I tried this query for search by author_id
#Query("select b from Books b where b.bookId in (select a.bookId from author_book a where a.author_id = :Id)")
but I'm getting error that author_book is not mapped so do I need to create another entity for this class to perform any operations b/w these 2 class? I'm also not sure if nested queries work in JPQL, this was basically a hunch.
My Books Entity
#Entity
#Table(name = "book")
public class Books implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="BOOK_ID")
private int bookId;
#Column(name="TITLE")
private String title;
#Column(name="SUBJECT")
private String subject;
#Column(name="PUBLISHED_YEAR")
private int publishedYear;
#Column(name="ISBN")
private String isbn;
#Column(name="QUANTITY")
private int quantity;
#Column(name="SHELF_DETAILS")
private String shelfDetails;
#Column(name="BOOK_COST")
private int bookCost;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "author_book",
joinColumns = { #JoinColumn(name = "BOOK_ID") },
inverseJoinColumns = { #JoinColumn(name = "AUTHOR_ID") })
private Set<Authors> authors = new HashSet<Authors>();
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="PUBLISHER_ID")
private Publishers publisher;
// getters and setters
}
My Authors Entity
#Entity
#Table(name="author")
public class Authors {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="AUTHOR_ID")
private int authorId;
#Column(name="FIRST_NAME")
private String firstName;
#Column(name="LAST_NAME")
private String lastName;
#Column(name="EMAIL")
private String email;
#Column(name="CONTACT_NO")
private String contactNo;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "authors")
private Set<Books> books = new HashSet<Books>();
// Getters and setters
}
You should add mappedBy things in Authors Entity.
#ManyToMany(mappedBy="authors", fetch=FetchType.LAZY)
private Set<Books> books = new HashSet<Books>();
Related
I am tasked to change the foreign key constraint on a bridge table to a unique key constraint via mysql script. I am on a spring boot project that uses flyway for database migrations.
This is the model class for the first table
#Data
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "payment_transaction_status_update")
public class PaymentTransactionStatusUpdate {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#Column(name = "file_tag")
private String fileTag;
#Column(name = "location")
private String location;
#Column(name = "file_key")
private String fileKey;
#Column(name = "file_name")
private String fileName;
#Column(name = "bucket")
private String bucket;
#Column(name = "server_side_encryption")
private String serverSideEncryption;
#Column(name = "batch_transaction_update_status")
#Enumerated(EnumType.STRING)
private BatchTransactionUpdateStatus batchTransactionUpdateStatus;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at", length = 19)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at", length = 19)
private Date updatedAt;
#Column(name = "initiator_email")
private String initiatorEmail;
#Column(name = "approver_email")
private String approverEmail;
}
This is the model class for the second table
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Data
#Entity
#Table(name = "payment_transaction")
public class AllPaymentTransaction implements Serializable, IPaymentTransaction {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "payable_type")
private String payableType;
#Column(name = "client_id")
private String clientId;
#Column(name = "payable_model_id")
private Long payableModelId;
#Column(name = "internal_reference", unique = true, nullable = false)
private String reference;
#Column(name = "provider_reference", length = 64)
private String providerReference;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at")
private Date createdAt;
}
So currently the following is the sql script for the bridge table
CREATE TABLE `payment_transaction_status_update_record_status` (
`id` int NOT NULL AUTO_INCREMENT,
`payment_transaction_status_update_id` int NOT NULL,
`payment_transaction_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE_01` (`id`),
KEY `Foreign_IDs_IDX_01`
(`payment_transaction_status_update_id`,`payment_transaction_id`),
KEY `FK_payment_transaction_id_idx_01` (`payment_transaction_id`),
CONSTRAINT `FK_payment_transaction_id_01` FOREIGN KEY (`payment_transaction_id`)
REFERENCES `payment_transaction` (`id`),
CONSTRAINT `FK_payment_transaction_status_update_id_01` FOREIGN KEY
(`payment_transaction_status_update_id`) REFERENCES
`payment_transaction_status_update` (`id`)
) ENGINE=InnoDB;
Now I want to use the 'reference' column with the 'unique' index as the foreign key on this bridge table.
I have done the follwing
ALTER TABLE `payment_transaction_status_update_record_status`
DROP COLUMN `payment_transaction_id`,
DROP CONSTRAINT `FK_payment_transaction_id_01`,
DROP CONSTRAINT `FK_payment_transaction_status_update_id_01`,
DROP KEY `FK_payment_transaction_id_idx_01`,
DROP KEY `Foreign_IDs_IDX_01`;
ALTER TABLE `payment_transaction_status_update_record_status`
ADD COLUMN `payment_transaction_reference` VARCHAR(255) NOT NULL,
ADD KEY `Foreign_IDs_IDX_02`
(`payment_transaction_status_update_id`,`payment_transaction_reference`),
ADD KEY `FK_payment_transaction_reference_referencex_01`
(`payment_transaction_reference`),
ADD CONSTRAINT `FK_payment_transaction_reference_01` FOREIGN KEY
(`payment_transaction_reference`) REFERENCES `payment_transaction`
(`internal_reference`);
The bridge class is as follows
#Data
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "payment_transaction_status_update_record_status")
public class PaymentTransactionStatusUpdateRecordStatus {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#ManyToOne
#JoinColumn(name = "payment_transaction_status_update_id", referencedColumnName = "id", nullable = false)
private PaymentTransactionStatusUpdate paymentTransactionStatusUpdate;
#ManyToOne
#JoinColumn(name = "payment_transaction_id", referencedColumnName = "id", nullable = false)
private AllPaymentTransaction paymentTransaction;
}
Now when trying to populate the bridge table I am setting only the reference column on the AllPaymentTransaction class as well as setting setting the PaymentTransactionStatusUpdate via the id. I have also changed it to
#ManyToOne
#JoinColumn(name = "payment_transaction_reference", referencedColumnName = "reference", nullable = false)
private AllPaymentTransaction paymentTransaction;
While trying to populate the bridge table I get this error.
org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : co.onefi.services.xps.models.PaymentTransactionStatusUpdateRecordStatus.paymentTransaction -> co.onefi.services.xps.models.AllPaymentTransaction"
For more info: I am setting only the reference on the AllTransactionPayment object of the
bridge table.
I am having two entities having one-to-many & many-to-one relationship.
One side:
#Entity
#Table(name = "GAME_BLIND_STRUCTURE")
#Builder
#Getter
#Setter
public class GameBlindStructureEntity implements Serializable {
private static final long serialVersionUID = -7800120016594245121L;
#Id
#Column(name = "BLIND_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long blindId;
#Column(name = "BLIND_STRUCTURE_NAME", unique = true)
private String blindStructureName;
#OneToMany(mappedBy = "gameBlindStructure")
private List<GameBlindStructureDetailsEntity> gameBlindStructureDetailsEntities;
}
Many-sided entity:
#Entity
#Table(name = "GAME_BLIND_STRUCTURE_DETAILS")
#Builder
#Getter
#Setter
public class GameBlindStructureDetailsEntity implements Serializable {
private static final long serialVersionUID = -7800120016594245121L;
#Id
#Column(name = "BLIND_DETAILS_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long blindDetailsId;
#ManyToOne
#JoinColumn(name = "BLIND_ID")
private GameBlindStructureEntity gameBlindStructure;
#Column(name = "LEVEL")
private String level;
#Column(name = "SMALL_BLIND")
private Integer smallBlind;
#Column(name = "BIG_BLIND")
private Integer bigBlind;
#Column(name = "ANTE")
private Integer ante;
#Column(name = "TIME_BANK")
private String timeBank;
#Column(name = "MINUTES")
private Integer minutes;
}
In the service method, I am trying to persist these entities to database.
public BlindStructureResponseDto createBlindStructure(BlindStructureDto blindStructureDto) {
GameBlindStructureEntity gameBlindStructureEntity = GameBlindStructureEntity.builder()
.blindStructureName(blindStructureDto.getName())
.build();
List<BlindStructureDetailsDto> blindStructureDetailsDtos = blindStructureDto.getBlindStructureDetailsDtos();
List<GameBlindStructureDetailsEntity> gameBlindStructureDetailsEntities = new ArrayList<>();
for(BlindStructureDetailsDto blindStructureDetailsDto : blindStructureDetailsDtos) {
GameBlindStructureDetailsEntity gameBlindStructureDetailsEntity = mapper.convertToGameStructureDetailsEntity(blindStructureDetailsDto);
gameBlindStructureDetailsEntity.setGameBlindStructure(gameBlindStructureEntity);
gameBlindStructureDetailsEntities.add(gameBlindStructureDetailsEntity);
}
gameBlindStructureEntity.setGameBlindStructureDetailsEntities(gameBlindStructureDetailsEntities);
GameBlindStructureEntity savedEntity = blindStructureRepository.save(gameBlindStructureEntity);
BlindStructureResponseDto blindStructureResponseDto = BlindStructureResponseDto.builder()
.name(savedEntity.getBlindStructureName())
.blindId(savedEntity.getBlindId())
.build();
return blindStructureResponseDto;
}
Though the entity on one-side is getting persisted to the database, the many sided entity is not getting saved.
Here is the ddl script:
DROP TABLE IF EXISTS `GAME_BLIND_STRUCTURE`;
CREATE TABLE `GAME_BLIND_STRUCTURE`
( `BLIND_ID` INT AUTO_INCREMENT,
`BLIND_STRUCTURE_NAME` VARCHAR(255) NOT NULL,
PRIMARY KEY (`BLIND_ID`),
UNIQUE KEY `UNIQUE_BLIND_STRUCTURE_NAME` (`BLIND_STRUCTURE_NAME`)
) ENGINE=INNODB DEFAULT CHARSET=latin1;
DROP TABLE IF EXISTS `GAME_BLIND_STRUCTURE_DETAILS`;
CREATE TABLE `GAME_BLIND_STRUCTURE_DETAILS`
(`BLIND_DETAILS_ID` INT AUTO_INCREMENT,
`BLIND_ID` INT NOT NULL,
`LEVEL` VARCHAR(255) NOT NULL,
`SMALL_BLIND` INT NOT NULL,
`BIG_BLIND` INT NOT NULL,
`ANTE` INT NOT NULL,
`TIME_BANK`VARCHAR(255) NOT NULL,
`MINUTES` INT NOT NULL,
PRIMARY KEY(`BLIND_DETAILS_ID`),
CONSTRAINT `FK_BLIND_ID` FOREIGN KEY (`BLIND_ID`) REFERENCES `GAME_BLIND_STRUCTURE` (`BLIND_ID`)
)ENGINE=INNODB DEFAULT CHARSET=latin1;
You're missing CascadeType.ALL on your #OneToMany annotation, code should be as follows:
#OneToMany(mappedBy = "gameBlindStructure", cascade = CascadeType.ALL)
private List<GameBlindStructureDetailsEntity> gameBlindStructureDetailsEntities;
When I generate the tables it generates it ok, but JPA is not generating any FK.
When I tell mysql to show me what it did to create the table it shows the lack of the foreign key constraint. It only generates KEY and it is not adding the foreign key.
mysql> show create table view_display;
| view_display | CREATE TABLE view_display (
dtype varchar(31) NOT NULL,
id bigint(20) NOT NULL,
col int(11) NOT NULL,
row int(11) NOT NULL,
chart_id bigint(20) DEFAULT NULL,
view_id bigint(20) NOT NULL,
data_source_id bigint(20) DEFAULT NULL,
PRIMARY KEY (id),
KEY FKqllm025dtc51xf38qdr5je52q (chart_id),
KEY FKb1gu01sld2cm281oa88pjq9ia (view_id),
KEY FK2hljr3ohtf3vql7hhvyqbm2mc (data_source_id)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 |
View.java
#Entity
#Table(name="view")
public class View {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "view_seq")
#SequenceGenerator(name = "view_seq", sequenceName = "view_seq", allocationSize = 1)
private Long id;
private String name;
#Column(name ="is_default",nullable= false,columnDefinition="tinyint(1) default 0")
private boolean isDefault;
#ManyToOne
#JoinColumn(name="userId",insertable=true, updatable= false,nullable=true)
private User user;
#OneToOne
#JoinColumn(name="cvId",insertable=true, updatable= false,nullable=true,unique=true)
private CV cv;
#OneToMany(mappedBy="view")
private List<ViewDisplay> viewDisplay;
// getters and setters....
}
ViewDisplay.java
#Entity
#Table(name = "view_display")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class ViewDisplay {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "view_display_seq")
#SequenceGenerator(name = "view_display_seq", sequenceName = "view_display_seq", allocationSize = 1)
private Long id;
#ManyToOne
#JoinColumn(name="chartId",insertable=true, updatable= true, nullable = true)
private Chart chart;
#ManyToOne
#JoinColumn(name = "viewId", insertable = true, updatable = true, nullable=false)
private View view;
private int col;
private int row;
public ViewDisplay() {}
public ViewDisplay(Long id, Chart chart, View view, int col, int row) {
super();
this.id = id;
this.chart = chart;
this.view = view;
this.col = col;
this.row = row;
}
// getters and setters...
}
I'm newb to JPA. I'm having trouble with defining OneToOne bidirectional flow on my classes.
UserInfo.java:
#Entity
#Table(name="UserInfo")
public class UserInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer idUserInfo;
private String firstName;
private String lastName;
#OneToOne(mappedBy="UserInfo")
private LoginInfo loginInfo;
LoginInfo.java:
#Entity
#Table(name="LoginInfo")
public class LoginInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer idLoginInfo;
#Column(name="emailId")
private String emailId;
//bidirectional one to one association to UserInfo
#OneToOne
#JoinColumn(name="emailId", referencedColumnName="loginInfo")
private UserInfo userInfo;
private String sessionId;
private String password;
Here's sql to create those tables:
CREATE TABLE `LoginInfo` (
`emailId` varchar(45) NOT NULL,
`idLoginInfo` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`idLoginInfo`),
UNIQUE KEY `emailId_UNIQUE` (`emailId`),
UNIQUE KEY `id_UNIQUE` (`idLoginInfo`)
);
DROP TABLE IF EXISTS `UserInfo`;
CREATE TABLE `UserInfo` (
`idUserInfo` int(11) NOT NULL AUTO_INCREMENT,
`firstName` varchar(45) NOT NULL,
`lastName` varchar(45) NOT NULL,
`loginInfo` varchar(45) NOT NULL,
PRIMARY KEY (`idUserInfo`),
UNIQUE KEY `idUserInfo_UNIQUE` (`idUserInfo`),
UNIQUE KEY `loginInfo_UNIQUE` (`loginInfo`),
CONSTRAINT `loginInfo` FOREIGN KEY (`loginInfo`) REFERENCES `LoginInfo` (`emailId`) ON DELETE CASCADE ON UPDATE CASCADE
);
When I start the Tomcat server, I'm getting the follow exception on ContextInitialization.
Caused by: org.hibernate.MappingException: Unable to find column with logical name: loginInfo in org.hibernate.mapping.Table(UserInfo) and its related supertables and secondary tables
But the column do exist on the UserInfo table. Can somebody please help me if the mapping is right?
Thanks
Invalid association : #JoinColumn(name="emailId", referencedColumnName="loginInfo")
#Entity
#Table(name="UserInfo")
public class UserInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer idUserInfo;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "userInfo", cascade = CascadeType.ALL)
private LoginInfo loginInfo;
#Entity
#Table(name="LoginInfo")
public class LoginInfo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer idLoginInfo;
#Column(name="emailId")
private String emailId;
//bidirectional one to one association to UserInfo
#OneToOne
#PrimaryKeyJoinColumn
private UserInfo userInfo;
private String sessionId;
private String password;
After reading about OneToOne mapping, annotations and some trial & error, I was able to use the LoginInfo and UserInfo tables successfully. In my question above, it has some invalid mappings. The requirement was that UserInfo be the owner entity and LoginInfo as the child entity. So as suggested by ashokhein, I used PrimaryKeyJoinColumn annotation. This is how my tables look like now.
CREATE TABLE `login_info` (
`user_info_id` bigint(100) NOT NULL,
`email_id` varchar(45) NOT NULL,
PRIMARY KEY (`user_info_id`),
UNIQUE KEY `email_id_UNIQUE` (`email_id`),
UNIQUE KEY `user_info_id_UNIQUE` (`user_info_id`),
CONSTRAINT `FK_login_info_user_info` FOREIGN KEY (`user_info_id`) REFERENCES `user_info` (`user_info_id`) ON DELETE CASCADE ON UPDATE CASCADE);
CREATE TABLE `user_info` (
`user_info_id` bigint(100) NOT NULL AUTO_INCREMENT,
`first_name` varchar(50) DEFAULT NULL,
`last_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`user_info_id`),
UNIQUE KEY `user_info_id_UNIQUE` (`user_info_id`));
This is how my classes look like:
UserInfo:
#Entity
#Table(name = "user_info")
public class UserInfo {
#Id
#Column(name = "user_info_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer userId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private LoginInfo loginInfo;
//Getters and setters
LoginInfo:
#Entity
#Table(name = "login_info")
public class LoginInfo {
#Id
#Column(name = "user_info_id")
#GeneratedValue(generator = "generator")
#GenericGenerator(name = "generator", strategy = "foreign", parameters = #Parameter(name = "property", value = "userInfo"))
private Integer id;
#OneToOne
#PrimaryKeyJoinColumn
private UserInfo userInfo;
#Column(name = "email_id")
private String emailId;
//Getters and setters
And this is how I create and save the entities to the table:
UserInfo userInfo = new UserInfo();
userInfo.setFirstName(registerUserRequest.getFirstName());
userInfo.setLastName(registerUserRequest.getLastName());
LoginInfo loginInfo = userInfo.getLoginInfo();
if(loginInfo == null) {
loginInfo = new LoginInfo();
}
loginInfo.setEmailId(registerUserRequest.getEmailId());
loginInfo.setUserInfo(userInfo);
userInfo.setLoginInfo(loginInfo);
if(userService.create(userInfo)) {
logger.debug("User created successfully");
} else {
throw new UserAlreadyExistException();
}
During the course of getting to this solution, I encountered bunch of hibernate exceptions and issues. Here's some(if it helps someone)
Error: identifier of an instance of was altered from 14 to 14
Solution: The primary key on the Entity classes where different types initially. The column user_info_id was declared as Integer in UserInfo and long in LoginInfo.
Error: attempted to assign id from null one-to-one property
Solution: While creating the UserInfo object, I did "userInfo.setLoginInfo(loginInfo)" but did not set "loginInfo.setUserInfo(userInfo)". After fixing that, it was fine.
Note: If you see a better way to do the same, please do comment here and let me know. Thanks in advance.
This is what my SQL tables look like:
CREATE TABLE IF NOT EXISTS `test`.`Families` (
`id` INT NOT NULL AUTO_INCREMENT,
`mother_id` INT DEFAULT NULL ,
`father_id` INT DEFAULT NULL ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `test`.`Parents` (
`id` INT NOT NULL AUTO_INCREMENT,
`first_name` VARCHAR(50) DEFAULT NULL,
`last_name` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
And this is what my family entity looks like:
#Entity
#Table(name="Families")
public class Family implements Serializable {
#Id
#Column(name="id")
private String id;
#Column(name="mother_id")
private int mother;
#Column(name="father_id")
private int father;
}
Which is great and all, but I would really LOVE if I could do something like this (note I also have a Parent entity already defined):
#Entity
#Table(name="Families")
public class Family implements Serializable {
#Id
#Column(name="id")
private String id;
#OneToOne
#Column(name="mother_id")
private Parent mother;
#OneToOne
#Column(name="father_id")
private Parent father;
}
How could I go about making this happen?
Actually, Hibernate does everything for you.
You don't need to annotate columns with #Column which already have #OneToOne or other association annotations
If you want to use other than default foreign key(by default name consists of field + _id), you should use #JoinColumn annotation
#Entity
#Table(name="Families")
public class Family implements Serializable {
#Id
#Column(name="id")
private String id;
#OneToOne
#JoinColumn(name = "mother_idd")
private Parent mother;
#OneToOne
#JoinColumn(name = "father_idd")
private Parent father;
}