I am new to Spring boot and I want to be able to delete the value of a forgein key if its entity is removed without deleting the whole entity linked to it; I explain in my case a single person who has an Account can be at the same time an Author and a Player, so if I delete an author I want to delete its refrence in Account table without deleting the whole account because this account can still point on player. I searched on the internet I found cascadetype but it will delete the whole account!
Thank you in advance!
here is my entities
#Table(name = "account")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
#ManyToOne
#JoinColumn(name = "Author")
private Author author;
#ManyToOne
#JoinColumn(name = "Player")
private Player player;
//attributs, getters & setters
}
#Table(name = "player")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Player implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
//attributs, getters & setters
}
//ma.myapp.usersgestion.domain.Author
#Table(name = "author")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "author")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIgnoreProperties(value = { "player", "author"}, allowSetters = true)
private Set<Account> accounts = new HashSet<>();
//attributs, getters & setters
}
UPDATE
Im using jhipster (spring boot with React) and h2 database (with disk-based persistence)
//AuthorResource.java
#RestController
#RequestMapping("/api")
#Transactional
public class AuthorResource {
private final Logger log = LoggerFactory.getLogger(AuthorResource.class);
private static final String ENTITY_NAME = "author";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final AuthorRepository authorRepository;
public AuthorResource(AuthorRepository authorRepository) {
this.authorRepository = authorRepository;
}
/**
* {#code DELETE /authors/:id} : delete the "id" author.
*
* #param id the id of the author to delete.
* #return the {#link ResponseEntity} with status {#code 204 (NO_CONTENT)}.
*/
#DeleteMapping("/authors/{id}")
public ResponseEntity<Void> deleteAuthor(#PathVariable Long id) {
log.debug("REST request to delete Author : {}", id);
authorRepository.deleteById(id);
return ResponseEntity
.noContent()
.headers(HeaderUtil.createEntityDeletionAlert(applicationName, true, ENTITY_NAME, id.toString()))
.build();
}
//...
}
//AuthorRepository
#SuppressWarnings("unused")
#Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {}
In your entity class author add the following:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "author", cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST })
private Set<Account> accounts;
I've omitted the cascadetype CascadeType.REMOVE from the list. This will prevent Account from also being deleted when the related Author entity is deleted.
EDIT:
If the above solution somehow doesn't work then you can also try adding #OnDelete(action = OnDeleteAction.NO_ACTION) above the accounts field.
#OnDelete is a hibernate specific annotation.
EDIT 2:
If none of the solutions provided above work then you can also consider making a javax.persistence.#PreRemove annotated method that manually sets the author field for each related Account to null. You place this method inside the Author class. A method that is annotated with #PreRemove will always run before the entity is deleted. So for Author you could use the following method to set all author_id fields to null.
#PreRemove
public void deleteAuthor(){
this.getAccounts().forEach(account -> account.setAuthor(null));
}
Related
I am getting field 'id' doesn't have a default value error in my Spring application.
I am trying to create an Applicant with #Post method but as I am creating the Applicant, new creditRating object needs to be created.
Here is the method
public Applicant create(ApplicantDTO applicantDTO) {
Applicant applicant = ApplicantMapper.toEntity(applicantDTO);
applicant.setCreditRating(creditRatingService.create());
return applicantRepository.save(applicant);
}
Here is my Applicant class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long identificationNumber;
private String firstName;
private String lastName;
private double monthlyIncome;
private String phoneNumber;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "credit_rating_id", referencedColumnName = "id")
private CreditRating creditRating;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "applicant_credit",
joinColumns = {#JoinColumn(name = "applicant_id")},
inverseJoinColumns = {#JoinColumn(name = "credit_id")}
)
private Credit credit;
}
And this is the create method for CreditRating object.
public CreditRating create() {
CreditRating creditRating = new CreditRating();
creditRating.setCreditRating(getRandomCreditRating());
return creditRatingRepository.save(creditRating);
}
I want this object to be created while creating an Applicant but somehow I think JPA can't generate the id for it as I am doing the creation like this.
As requested here is CreditRating Entity
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "credit_rating")
public class CreditRating {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int creditRating;
}
I figured out the problem. Problem was in database creditRating and credit somehow didn't have Auto Increment ticked. I dropped the schema then let JPA create the tables again. Then Auto Increment was both ticked on credit and creditRating tables.
I have two entities called Appointement and Client, and I would like to get all the
appointements of a given client. I am struggling with the following error message when I try to do so.
2022-05-24 13:30:41.685 WARN 23252 --- [ XNIO-3 task-1]
.m.m.a.ExceptionHandlerExceptionResolver : Resolved
[org.springframework.dao.InvalidDataAccessApiUsageException: Parameter
value [1000] did not match expected type [ma.mycom.myapp.domain.Client
(n/a)]; nested exception is java.lang.IllegalArgumentException:
Parameter value [1000] did not match expected type
[ma.mycom.myapp.domain.Client (n/a)]]
Here are my entities:
Client.java
#Entity
#Table(name = "CLIENTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
//other attributes, getters, setters
}
Appointment.java
#Entity
#Table(name = "APPOINTMRNTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Appointment implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
//other attributes
#ManyToOne
#JoinColumn(name = "ID_CLIENT")
private Client client;
//getters, setters
}
Controller, and how I call the query:
List<Appointement> clientAppointements = appointementRepository.findAllByClient(idClient);
Here is the query used in AppointementRepository.java (I guess it's the source of the problem)
#Query(
name = "SELECT pe.* FROM APPOINTEMENTS pe INNER JOIN CLIENTS e ON pe.ID_CLIENT = e.ID WHERE e.id = ?1",
nativeQuery = true
)
List<Appointement> findAllByClient(Long idClient);
Your Appointment class does not have a field called "client id" of any sort, it is only aware of the Client entity it has.
In your JPA repository methods, you can only use the existing fields of an entity.
Two of the most standard ways you can fix this are:
1- Adding Appointment field to the Client side of the relationship to make it bidirectional (I recommend this personally). Your Client entity will look like this:
#Entity
#Table(name = "CLIENTS")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Client implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "client", fetch = FetchType.LAZY)
List<Appointment> appointments;
//other attributes, getters, setters
}
Then you can simply get appointments by fetching a Client object, then simply accessing its appointments field.
List<Appointement> clientAppointements = clientRepository.findClientByField(field)
.getAppointments();
or you can even get appointments of a client in repository methods:
// in your appointment repository
#Query(value = "SELECT c.appointments FROM Client c WHERE c.id = :cId")
List<Appointment> getAppointmentsById(Long cId);
2- If you don't want to make the relationship bidirectional, you should fetch the Client object before searching it with the Appointment repository methods.
// in your appointment repository
List<Appointment> findByClient(Client client);
// then you can fetch it anywhere
Client client = clientRepository.findById(cliendId);
List<Appointment> clientAppointments = appointmentRepository.findByClient(client);
In my spring boot project, I have one LineItem entity below is the code
#Entity
#Table(name = "scenario_lineitem")
#Data
#NoArgsConstructor
public class LineItem implements Cloneable {
private static Logger logger = LoggerFactory.getLogger(GoogleConfigConstant.class);
#Id
#GeneratedValue(strategy = IDENTITY)
private BigInteger lineItemId;
#Column
private String name;
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "line_item_meta_id")
private List<QuickPopValue> quickPopValues;
}
Another entity is
#Entity
#Table(name = "quick_pop_value")
#Data
#NoArgsConstructor
public class QuickPopValue implements Cloneable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "quick_pop_value_id", columnDefinition = "bigint(20)", unique = true, nullable = false)
private BigInteger quickPopValueId;
#Column(name = "column_name")
private String columnName;
#Column(name = "value")
private String value;
#Column(name = "formula", columnDefinition = "longtext")
private String formula;
}
Now I am trying to delete QuickPopValue one by one but it's not getting deleted and not getting any exception as well.
Below is the delete code :
List<QuickPopValue> quickPopValues = sheetRepository.findByColumnName(columnName);
for (QuickPopValue qpValue : quickPopValues) {
quickPopValueRepository.delete(qpValue);
}
Such behavior occurs when deleted object persisted in the current session.
for (QuickPopValue qpValue : quickPopValues) {
// Here you delete qpValue but this object persisted in `quickPopValues` array which is
quickPopValueRepository.delete(qpValue);
}
To solve this you can try delete by id
#Modifying
#Query("delete from QuickPopValue t where t.quickPopValueId = ?1")
void deleteQuickPopValue(Long entityId);
for (QuickPopValue qpValue : quickPopValues) {
quickPopValueRepository.deleteQuickPopValue(qpValue.getQuickPopValueId());
}
I'm trying to create a social app service. I have user with confirmed or nonconfirmed relationships.
When I load UserA, the result look like belove.
"result":{
"idUser":"UserA",
"unconFriendships":[
{
"idUser":"UserB",
"unconFriendships":[
{
"idUser":"UserC",
"unconFriendships":[
...
While it has to be look like
"result":{
"idUser":"UserA",
"unconFriendships":[
{
"idUser":"UserB",
"unconFriendships":null //only one level have to fetched
....
I thought that this was because jackson json library, I debbuged the code. Before serialization, I inspected userA object and I saw that userA.unconFriendships.userB.unconFriendships was not null and with size bigger than 0.
Nearly it has been 12 hours, still couldn't solve the problem. Please help me to solve this. Thanks in advence.
Here is UserEntity.java
#Entity
#Table(name="aduser",uniqueConstraints=#UniqueConstraint(columnNames = {"idUser","nmEmail"}))
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="cdUser")
public class UserEntity extends BaseEntity {
protected static final long serialVersionUID = 8864033727886664353L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "admin_seq")
#SequenceGenerator(name = "admin_seq", sequenceName = "CDUSER_SEQUENCE", allocationSize = 1)
#Column(name="cdUser")
private long cdUser;
#OneToMany(mappedBy = "owner", targetEntity=Friendship.class)
#JsonProperty
protected Set<UnconfirmedFriendship> unconFriendships;
#OneToMany(mappedBy = "owner", targetEntity=Friendship.class)
#JsonProperty
protected Set<UnconfirmedFriendship> conFriendships;
...
Friendship.java
#Entity
#Table(name="aduserfriend")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "verified")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="friend_cduser",scope=UserEntity.class)
public abstract class Friendship extends BaseEntity{
protected static final long serialVersionUID = -670863816551430192L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "cdFriendship")
private long cdFriendship;
#ManyToOne
#JsonIgnore
#JoinColumn(name = "owner_cduser")
protected UserEntity owner;
#ManyToOne
#JoinColumn(name = "friend_cduser")
protected UserEntity friend;
#Column(name = "verified",insertable=false,updatable=false)
private boolean verified;
...
UnconfirmedFriendship.java and ConfirmedFriendship.java
#Entity
#DiscriminatorValue(value = "0")//this value is 1 for Confirmed relationship
public class UnconfirmedFriendship extends Friendship {
private static final long serialVersionUID = 57796452166904132L;
}
I have a circular dependency that I am struggling to solve right now
Take these two classes - boiler plate code removed for demo purposes
Class 1
#Entity
#Table(name = "T_CREDENTIAL")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class Credential implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//Removed #JsonIgnore as want to disable certain fields if no credentials are available
//Also fetch this in an eager fashion instead of lazily loading it
#OneToMany(mappedBy = "credential",fetch=FetchType.EAGER)
private Set<UserTask> userTasks = new HashSet<>();
....
.....
Class 2
#Entity
#Table(name = "T_USERTASK")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="#id")
public class UserTask implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8179545669754377924L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#NotNull(message = "Credential must not be null")
private Credential credential;
Unfortunately I have use cases where UserTask needs to load the credentials and cases where Credential needs to load the Usertasks
The annotation #JsonIdentityInfo seems to be doing its job
If i load all UserTasks, it loads the first userTask and its credential, but then that credential object will also load any UserTasks that are assigned to that credential. This then encounters the new #Id or a userTask which now loads it with the credential instead of as 2 users tasks
Any ideas what I can do to circumvent this problem?
Cheers
Damien
--Question Update
I have updated my code now to use the new annotations as mentioned by other users
Class 1
#Entity
#Table(name = "T_CREDENTIAL")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Credential implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8696943454186195541L;
//Removed #JsonIgnore as want to disable certain fields if no credentials are available
//Also fetch this in an eager fashion instead of lazily loading it
#OneToMany(mappedBy = "credential",fetch=FetchType.EAGER)
#JsonManagedReference("credential")
private Set<UserTask> userTasks = new HashSet<>();
....
....
Class 2
#Entity
#Table(name = "T_USERTASK")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class UserTask implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#NotNull(message = "Credential must not be null")
#JsonBackReference("credential")
private Credential credential;
....
....
These classes now now work and have no circular dependencies
However, when i do a query now to get my usertasks or a single task, the credential object is not returned even though I do not have the #jsonIgnore annotation specified. Any ideas what is causing this?
if you are using Jackson HttpMessageConverter, you should check their documentation - http://wiki.fasterxml.com/JacksonFeatureBiDirReferences
You should add annotations shown here:
public class NodeList
{
#JsonManagedReference
public List<NodeForList> nodes;
}
public class NodeForList
{
public String name;
#JsonBackReference public NodeList parent;
public NodeForList() { this(null); }
public NodeForList(String n) { name = n; }
}
I went with the annotation below in the end
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
Thanks for your help guys