I'm trying insert the id of Person in child table 'Contact'. But the Hibernate stores the value null at fk column.
I perform the mapping of a DTO to the entity, where it already brings the person's data and contact. In the end I have to save the person entity.
There is a table inheritance!
Parent table:
#Entity
#Table
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING, columnDefinition = "CHAR(2)", length = 2)
public abstract class Person implements Serializable {
#OneToMany(fetch = FetchType.EAGER, mappedBy = "person")
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
private #Getter #Setter Set<Contact> contacts;
}
Table Company extends of Person:
#Entity
#Table
#DiscriminatorValue(value="PJ")
public class Company extends Person implements Serializable {
#Column(nullable = false, columnDefinition = "DATE")
private #Getter #Setter LocalDate constitutionDate;
}
And where is the problem!
#Entity
#Table(name = "contact")
#EqualsAndHashCode
public class Contact implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private #Getter Integer id;
#Column(columnDefinition = "VARCHAR(16)", length = 16, nullable = false)
private #Getter #Setter String phoneNumber;
#ManyToOne(fetch = FetchType.LAZY, optional = false,targetEntity=Person.class)
#JoinColumn(name = "person_id", nullable = false, referencedColumnName="id")
private #Getter #Setter Person person;
public Contact() {}
public Contact(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
What am I doing wrong? What's the best strategy?
Excuse me for my poor English!
Can't find nothing obvious that's wrong but let me try some things:
Your cascade annotation on Person class doesn't need the specific Hibernate enumeration You can use like :
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "cliente")
In your ManyToOne annotation on Contact class please try to add:
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
BTW, an inheritance relationship between Person and Company doesn't seem logic to me, but that has nothing to do with your stated problem for sure.
Related
I have a few models that has relationship to each other within this object I am tryin to save and fetch from the database, but I can't seem to get it to work properly. The object contains a property that also contains another object. My approach was to save to all related models and for those models, I would also save this current model to them as well so it shows relation on the database table.
Below the models for the app.
#Entity
#Table(name = "daily_entry")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "daily_entry_id", unique = true)
private Long id;
#Column(unique = true)
private LocalDate date;
private int weight;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "daily_macros_id", unique = true)
private DailyMacros dailyMacros;
#OneToMany(mappedBy = "dailyEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Exercise> exercise = new ArrayList<Exercise>();
}
#Entity
#Table(name = "daily_macros")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyMacros {
#Id
#GeneratedValue
#Column(name = "daily_macros_id")
private Long id;
private int calories;
private int protein;
private int fat;
private int carbs;
#OneToOne(mappedBy = "dailyMacros", cascade = CascadeType.ALL, orphanRemoval = true)
private DailyEntry dailyEntry;
}
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name= "exercises")
public class Exercise {
#Id
#GeneratedValue
#Column(name = "exercise_id")
private Long id;
private String name;
private int sets;
private int reps;
private int weight;
#ManyToOne
#JoinColumn(name = "daily_entry_id", unique = true)
private DailyEntry dailyEntry;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
private String role = "user";
private String token;
#Column(unique = true, nullable = false)
protected String username;
#Column(unique = true ,nullable = false)
private String emailAddress;
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "macros_goal_id")
private MacrosGoal macrosGoal;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DailyEntry> dailyEntry = new ArrayList<>();
}
Im tryin to save the model of DailyEntry to the database.
User is the owner of DailyEntry
DailyEntry owns MacrosGoal (#OneToOne) and Exercise (#OneToMany). How exactly can I save and fetch this model into the database?
This is what I've tried -
For my PutMapping, I get this error : Cannot call sendError() after the response has been committed
My GetMapping just returns a empty response although http status code 200
DailyEntryController file
#RequestMapping(path = "/api/v1/")
#RequiredArgsConstructor
public class DailyEntryController {
#Autowired
DailyEntryService dailyEntryService;
#GetMapping("/getDailyEntry")
public ResponseEntity<DailyEntry> getDailyEntry(#RequestParam("username") String username, #RequestParam String date) throws ResponseStatusException {
DailyEntry dailyEntry = dailyEntryService.getDailyEntry(username, date);
return new ResponseEntity<>(dailyEntry, HttpStatus.OK);
}
#PutMapping("/addDailyEntry")
public ResponseEntity<DailyEntry> addDailyEntry(#RequestBody DailyEntry dailyEntry, #RequestParam("username") String username) throws ResponseStatusException {
DailyEntry dailyEntryInfo = dailyEntryService.createDailyEntry(dailyEntry, username);
return new ResponseEntity<>( dailyEntryInfo, HttpStatus.OK);
}
}
#Slf4j
#Service
public class DailyEntryServiceImpl implements DailyEntryService {
#Autowired
DailyEntryRepository dailyEntryRepository;
#Autowired
ExerciseRepository exerciseRepository;
#Autowired
UserRepository userRepository;
#Autowired
DailyMacrosRepository dailyMacrosRepository;
#Override
public DailyEntry addExercise(Exercise exercise) {
return null;
}
#Override
public DailyEntry getDailyEntry(String username, String date) {
DailyEntry entry = null;
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
LocalDate localDate = LocalDate.parse(date);
List<DailyEntry> dailyEntryList = user.getDailyEntry();
for(DailyEntry e : dailyEntryList) {
if(e.getDate() == localDate){
entry = e;
}
}
return entry;
}
#Override
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
DailyEntry entry = new DailyEntry();
// Find user
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
entry.setUser(user);
entry.setDate(LocalDate.parse(dailyEntry.getDate().toString()));
entry.setDailyMacros(dailyEntry.getDailyMacros());
entry.setWeight(dailyEntry.getWeight());
entry.setExercise(dailyEntry.getExercise());
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(entry);
dailyMacrosRepository.save(dailyMacros);
List<Exercise> exercise = dailyEntry.getExercise();
for (Exercise e : exercise) {
e.setDailyEntry(entry);
exerciseRepository.save(e);
}
List<DailyEntry> dailyEntryList = user.getDailyEntry();
dailyEntryList.add(entry);
user.setDailyEntry(dailyEntryList);
userRepository.save(user);
return entry;
}
I also tried with Query inside repository, but I may have done it wrong.
You should probably add #Transactional to createDailyEntry.
I don't really understand what's happening in createDailyEntry but I think it should look something like this:
#Override
#Transactional
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
User user = userRepository
.findByUsername(username)
.get();
dailyEntryRepository.save(dailyEntry);
user.getDailyEntry().add(dailyEntry);
dailyEntry.setUser(User);
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(dailyEntry);
dailyMacrosRepository.save(dailyMacros);
dailyEntry.getExcercise()
.forEach( e -> {
e.setDailyEntry(dailyEntry);
exerciseRepository.save(e);
});
// I don't think you need this because user is already managed
// and userFromDatabase.get() will throw an exception
// if the user does not exist.
// userRepository.save(user);
return dailyEntry;
}
I think getDailyEntry doesn't work because you are using e.getDate() == localDate and it's always going to be false.
One solution is to add a method to the DailyEntryRepository that accept username and date:
interface DailyEntryRepository extends JpaRepository<DailyEntry, Long> {
#Query("select de from DailyEntry de where de.user.username = :username and de.date = :localDate")
DailyEntry findByUsernameAndDate(#Param("username") String username, #Param("localDate") LocalDate date);
}
...
#Override
public DailyEntry getDailyEntry(String username, String date) {
LocalDate localDate = LocalDate.parse(date);
return dailyEntryRepository.findByUsernameAndDate(username, localdate);
}
This solution will run a query and load only the entry you need.
But, if you need to validate the username, this should work:
#Override
public DailyEntry getDailyEntry(String username, String date) {
User user = userRepository
.findByUsername(username)
.get();
LocalDate localDate = LocalDate.parse(date);
for(DailyEntry e : user.getDailyEntry()) {
if (localDate.equals(e.getDate)){
// No need to continue the loop, you've found the entry
return entry;
}
}
// daily entry not found
return null;
}
Another thing that's missing is that in your model you are not handling the bidirectional association during convertion to JSON.
The solution is to use I end up using #JsonManagedReference and #JsonBackReference where you have the association.
I am implementing a Spring Boot server using JPA and Hibernate where there are 2 entities: Channel and Translation.
The Channel entity has two fields (nameTranslations and descriptionTranslations that should hold the name and description of a channel in 2 languages french and english) which are of type Translation as described as follow:
Class Channel
#Entity
#Table(name = "CHANNEL")
public class Channel {
#Id
#Column(name = "ID")
private String id;
#OneToOne(mappedBy = "channel", cascade = CascadeType.ALL)
private Translation nameTranslations;
#OneToOne(mappedBy = "channel", cascade = CascadeType.ALL)
private Translation descriptionTranslations;
}
and
Class Translation
#Entity()
#Table(name = "TRANSLATION")
public class Translation {
#Id
#Column(name = "ID")
private String id;
#Column(length = 1024)
private String en;
#Column(length = 1024)
private String fr;
}
My issue is: How can I implement the previously described logic so that there are 2 Translation fields in the Channel class? I have tried it so far using #OneToOne annotation, but it doesn't work.
I'm not sure what kind of mapping you are trying to achieve, but this will work:
#Entity
#Table(name = "CHANNEL")
public class Channel {
#Id
#Column(name = "ID")
private String id;
#OneToOne(cascade = CascadeType.ALL)
private Translation nameTranslations;
#OneToOne(cascade = CascadeType.ALL)
private Translation descriptionTranslations;
}
or, if you want the columns on the other entity table:
#Entity
#Table(name = "CHANNEL")
public class Channel {
#Id
#Column(name = "ID")
private String id;
#OneToOne(mapped="name", cascade = CascadeType.ALL)
private Translation nameTranslations;
#OneToOne(mapped="description", cascade = CascadeType.ALL)
private Translation descriptionTranslations;
}
#Entity
#Table(name = "TRANSLATION")
public class Translation {
#Id
#Column(name = "ID")
private String id;
#Column(length = 1024)
private String en;
#Column(length = 1024)
private String fr;
#OneToOne
private Channel name;
#OneToOne
private Channel description;
}
See the Hibernate ORM documentation for one-to-one associations.
I have a User model that contains a list of achievements
#Table(name = "user")
#Entity
#NamedEntityGraph(name = "User.achievements",
attributeNodes={
#NamedAttributeNode("achievements")
})
#Data
public class User {
#Id
#NotNull
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ElementCollection(fetch = FetchType.LAZY, targetClass = Achievement.class)
private List<Achievement> achievements = new ArrayList<>();
}
Here's the achievement model
#Entity
#Data
#Table(name = "achievement")
public class Achievement {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String achievementId;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "achieved", columnDefinition="BOOLEAN DEFAULT false", nullable = false)
private boolean achieved = false;
user_achievements table generated from #ElementCollection mapping, which atm only contains user and achievement foreign keys
I am looking to move the boolean achieved value to the user_achievements table, ideally without having to create a separate model User_Achievements
I am fairly new to using Jpa, but i feel like this scenario is too basic so there must be a straight forward way to do that i cant seem to locate it
#Entity
class UserAchievement {
#EmbeddableId
UserAchievementId id;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="user_username", insertable=false, updatable=false)
User user;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="achivement_achivement_id", insertable=false, updatable=false)
Achivement achivement;
// and other fields
}
class User {
// ...
#OneToMany(mappedBy="user")
List<UserAchievement> userAchievements;
}
and you need to define UserAchievementId
I'm trying to do a simple #OneToMany relationship between contract and asset. But when hibernate tries to save , it's comming as null. What am I doing wrong?
#Entity
#Data
#EqualsAndHashCode
#NoArgsConstructor
#Table(name = "contracts")
public class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contractId")
private List<Asset> assets;
}
#Entity
#Data
#Table(name = "assets")
public class Asset {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "contractId", referencedColumnName = "id")
private Contract contractId;
}
#Repository
public interface ContractRepository extends CrudRepository<Contract, Integer> {
}
private void mapAndSave(ContractDTO contractDTO) {
Contract contractToSave = new Contract();
ModelMapper mapper = BiModelMapper.createModelMapperDtoToEntity();
mapper.map(contractDTO, contractToSave);
contractRepository.save(contractToSave);
}
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'contractId' cannot be null
The solution I was able to do is change my column table Asset.contractId to NOT NULL. Because Hibernate tries to insert the row, and after that updates the contractId.
And I change to unidirectional relationship, using only #OneToMany on Contract side.
I have simple code like:
#Transactional(readOnly = true, timeout = 10)
public List<MyClass> findThem() {
Criteria criteria = getSession().createCriteria(MyClass.class);
#SuppressWarnings("unchecked")
List<MyClass> theList = criteria.list();
return theList;
}
When I use this for the query, I get back 942 items, with duplicated items in the list. I don't see the pattern about which ones are duplicated just yet.
If I query with DB directly, I get back 138 records. 138 is the correct number of results:
SELECT count(*) FROM theschema.MY_CLASS;
I tried using the class with and without Lombok's #EqualsAndHashCode(exclude="id") on MyClass and also just not including any equals() implementation, thinking this was the cause - but no luck.
I've recently upgraded from MySQL 5.6 to MySQL 5.7.17 but I'm still on Hibernate 4.3.8.Final and HikariCP 2.5.1. The query has no WHERE clauses or anything very complicated. It may not matter, but I'm on Spring 4.3.3.RELEASE with mysql-connector-java 5.1.39.
Any other suggestions where to look? I'm going to turn on more debug logging and take a closer look in the meantime.
Update:
I'm able to correct this with criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY); but not sure why it's now necessary.
Update 2
Here's MyClass. It has more basic properties, but I excerpted them for brevity. Some of the names are renamed for privacy reasons.
import base.domain.model.Persistable;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.NotEmpty;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.*;
#NoArgsConstructor
#Entity
#Table(name = "MY_CLASS")
public class MyClass implements Persistable {
#Getter
#Setter
#Id
#Column(name = "MY_CLASS_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Getter
#Setter
private ClassB classb;
#Getter
#Setter
#ManyToOne
#JoinColumn(name = "CLASS_C_ID")
private ClassC classC;
#Getter
#Setter
#Transient
private Integer daysOpen;
#Getter
#Setter
#OneToOne
#JoinColumn(name = "CLASS_D_ID")
private ClassD classD;
#Getter
#Setter
#NotEmpty
private String briefDescription;
#Getter
#Setter
#Column(length = 1000)
private String details;
#Getter
#Setter
#OneToOne
#JoinColumn(name = "COMPANY_ID")
private Company company;
#Getter
#Setter
#Transient
private BigDecimal cost;
#Getter
#Setter
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "MY_CLASS_TAG",
foreignKey = #ForeignKey(name = "FK_MY_CLASS_TAG_REQUEST"),
joinColumns = #JoinColumn(name = "MY_CLASS_ID"),
inverseJoinColumns = #JoinColumn(name = "TAG_ID"))
private Set<Tag> tags = new HashSet<>(10);
#Getter
#Setter
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "MY_CLASS_ASSIGNEE",
foreignKey = #ForeignKey(name = "FK_MY_CLASS_ASSIGNEE"),
joinColumns = #JoinColumn(name = "MY_CLASS_ID"),
inverseJoinColumns = #JoinColumn(name = "ASSIGNEE_ID"))
private Set<Account> assignees = new HashSet<>(0);
#Getter
#Setter
#OneToMany(cascade = CascadeType.ALL, mappedBy = "myClass")
private List<MyClassHistory> historyList = new ArrayList<>(1);
private static final long serialVersionUID = 1L;
}
Update 3:
I am also able to get the correct results using HQL (abridged) without the result transformer:
String hql = "from MyClass myClass";
Query q = getSession().createQuery(hql);
List<MyClass> myClasses = q.list();
return myClasses;
Going to compare the SQL output for each (there's a lot of output to try to understand).
there must be some join happening in the query, which increases resultset and hence your count,often I face similar issue, you can try to take data in Set instead of list, or observe the actual query fired in DB.