I am new to hibernate and was working on a project where I want to update the expiry date of membership when someone buys the membership. i have written a class implementing the AtributeConverter.
Upon calling the buyMembership method, both print methods are printing the correct date but database is not getting updated. In console, it shows that only select query is running. The update query is being executed. Refer code for referrence
USER_LOGIN Class
#Entity
#Table(name = "login")
public class User_Login {
#Id
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "enabled")
private Boolean enabled = false;
#Column(name="start_date")
private LocalDate startDate;
#Column(name="end_date")
private LocalDate expiryDate;
Repository
public void buyMembership(String months,String email) {
Session currentSession = manager.unwrap(Session.class);
User_Login user = currentSession.get(User_Login.class, email);
LocalDate date = user.getExpiryDate();
LocalDate current = LocalDate.now();
if(date.compareTo(current)<0) {
user.setStartDate(current);
LocalDate newExpiryDate = current.plusMonths(Long.parseLong(months));
System.out.println(newExpiryDate);
user.setExpiryDate(newExpiryDate);
user.setStartDate(current);
//currentSession.saveOrUpdate(user);
}
else {
LocalDate newExpiryDate = date.plusMonths(Long.parseLong(months));
System.out.println(newExpiryDate);
user.setExpiryDate(newExpiryDate);
user.setStartDate(current);
System.out.println(user.getExpiryDate());
//currentSession.saveOrUpdate(user);
}
currentSession.update(user);
}
Attribute Converter*
#Converter(autoApply = true)
public class LocalDatePersistenceConverter implements
AttributeConverter<LocalDate, Date> {
#Override
public Date convertToDatabaseColumn(LocalDate attribute) {
if(attribute==null)
return null;
return java.sql.Date.valueOf(attribute);
}
#Override
public LocalDate convertToEntityAttribute(java.sql.Date dbData) {
if(dbData==null)
return null;
return dbData.toLocalDate();
}
}
Actually, it was a silly error. I had just missed marking my buyMembership service as transactional. After that,It worked like a charm...
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'm saving a java object as json in my db. For this implementation when I save it, it works fine and I can find the new row in my db. However whenever I try to fetch the same object I stored, I get this error:
java.lang.NullPointerException: null
at java.lang.Class.isAssignableFrom(Native Method)
at com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor.fromString(JsonTypeDescriptor.java:104)
at com.vladmihalcea.hibernate.type.json.internal.JsonTypeDescriptor.wrap(JsonTypeDescriptor.java:165)
at com.vladmihalcea.hibernate.type.json.internal.AbstractJsonSqlTypeDescriptor$1.doExtract(AbstractJsonSqlTypeDescriptor.java:34)
at org.hibernate.type.descriptor.sql.BasicExtractor.extract(BasicExtractor.java:47)...
My hibernate versions are:
Hibernate Core {5.3.7.Final}
Hibernate Commons Annotations {5.0.4.Final}
And I'm using this library to serialize my java class to json.
com.vladmihalcea:hibernate-types-52:2.17.3
What is weird is that I can save entities to the database (can deserialize) but it cannot build the object back when I try to get it.
#Getter
#Setter
#TypeDefs({
#TypeDef(name = "json", typeClass = JsonType.class)
})
public class NotificationMessage implements Serializable {
private Long id;
private String name = "";
private String type;
}
#Entity
#Table(name = "notification")
#Getter
#Setter
#EntityListeners(AuditingEntityListener.class)
public class Notification {
#Id
#Column(name = "notification_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_notification_sequence")
#SequenceGenerator(name = "id_notification_sequence", sequenceName = "id_notification_sequence", allocationSize = 1)
private Long id;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
#NotNull
private NotificationType type;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_uuid", referencedColumnName = "user_uuid")
private User user;
#Type(type = "json")
#Column(name = "message", columnDefinition = "json")
#NotNull
private NotificationMessage message;
#Column(name = "acknowledged")
private boolean acknowledged = false;
#Column(name = "created_date", nullable = false, updatable = false)
#CreatedDate
private LocalDateTime createdDate;
#Column(name = "modified_date")
#LastModifiedDate
private LocalDateTime modifiedDate;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Notification that = (Notification) o;
return id.equals(that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
This is the query I'm running to get the notification back:
#Repository
public interface NotificationRepository extends JpaRepository<Notification, Long>, JpaSpecificationExecutor<Notification> {
#Query("SELECT n FROM Notification n WHERE n.type = :type AND n.acknowledged = false")
List<Notification> findAllByTypeAndAcknowledgedIsFalse(#Param("type") NotificationType type);
}
You must also declare the Hibernate Type to the entity class using the #TypeDef annotation like this:
#Entity
#Table(name = "notification")
#Getter
#Setter
#EntityListeners(AuditingEntityListener.class)
#TypeDef(name = "json", typeClass = JsonType.class)
public class Notification {
I had the same issue as you, and was stuck on it full day, really frustrating. But now I have fixed it. My fixes are:
Match the versions. I was using Hibernate 5.1 and hypersistence-utils-hibernate-55. However, according to the README in the repo, I should use io.hypersistence: hypersistence-utils-hibernate-5: 3.0.1 with Hibernate 5.1. However, in your case, I would try to unify the Hibernate versions first, and then choose the matching version for hypersistence-utils.
Use the correct annotation. In my Entity, I was annotating the field #Type(type = "jsonb"). After changing it to #Type(type = "json"). It started to work.
Hopefully, this can help you.
I have this JPA Class, where I have 3 columns id, name and date. The Database is already filled with data, where each entry has an id.
#Data
#Entity
#Table(name = "TEST", schema = "TESTSCHEMA")
public class TestDataJpaRecord implements Serializable {
private static final long serialVersionUID = 1L;
TestDataJpaRecord(){
// default constructor
}
public TestDataJpaRecord(
String name,
Date date,
){
this.name = name;
this.date = date;
}
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "TEST_SEQUENCE")
#SequenceGenerator(
sequenceName = "TEST_SEQUENCE", allocationSize = 1,
name = "TEST_SEQUENCEx")
private Long id;
#Column(name = "NAME")
private String name;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE")
private Date date;
}
I created a JPA repository for all the data.
public interface TestDataJpaRecordRepository extends JpaRepository<TestDataJpaRecord, Long> {
}
I want to get the data from the DB in a JSON format.
Here is my Rest GET Api. Here I return the data as a string just, but I want to return them as JSON.
#GetMapping(value = "data/{id}")
private ResponseEntity<?> getDataFromTheDB(#PathVariable("id") Long id) {
// get one entry form the DB
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
// Here I want to return a JSON instead of a String
return new ResponseEntity<>(testDataJpaRecord.toString(), HttpStatus.OK);
}
Any idea on how I could return the data as JSON and not as a string from the DB?
I would very very much appreciate any suggestion.
If you have Jackson on the classpath which you should if you have used the spring-boot-starter-web then simply:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord> getDataFromTheDB(#PathVariable("id") Long id) {
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
return new ResponseEntity.ok(testDataJpaRecord);
}
This assumes you have annoted your controller with #RestController rather than #Controller. If not then you can either do that or, annotate your controller method with #ResponseBody.
With Spring Data's web support enabled (which it should be by default with Spring Boot) then you can also simplify as below:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord>
getDataFromTheDB(#PathVariable("id") TestDataJpaRecord record) {
return new ResponseEntity.ok(record);
}
See:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.basic.domain-class-converter
I am building a simple get rest call from MySQL database, the problem is that it returns an empty object.
The call itself is takes in an email (I know this is not the best approach), here is my code:
Entity:
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
private String email;
private String password;
private String firstName;
private String userName;
private String lastName;
private boolean active;
#Temporal(TemporalType.DATE)
private Date createDate;
#Temporal(TemporalType.DATE)
private Date updateDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Collection<Role> roles;
// constructor
// get and setter
}
Repository:
public interface UserRepository extends JpaRepository<User, Long> {
// User findById (Integer Id);
#Query("SELECT u.id FROM User u where u.id = :id")
User findById(#Param("id") Integer id);
User findByEmail (String email);
}
Service:
#Service("userService")
public class UserService {
private String status, message;
private final HashMap map = new HashMap();
#Autowired
private UserRepository userRepository;
// #Autowired
// private RoleRepository roleRepository;
public User findByUserEmail (String email) {
return userRepository.findByEmail(email);
}
}
Controller:
#RestController("userControllerService")
#RequestMapping("/user/account")
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/test-get/{email}")
public User jj(#PathVariable("email") String email){
return userService.findByUserEmail(email);
}
}
And my database happens to have the following data:
And here is the response I get after hitting the URL
I have no clue why my response is empty!
You cannot have the #in the URI path. Encode it with %40.
Reference: Can I use an at symbol (#) inside URLs?
Also, right way is to use as a query param as that's more of a good identifier and allows # as it parses as string
#GetMapping("/test-get")
public User jj(#RequestParam("email") String email){
return userService.findByUserEmail(email);
}
Either ways, hit as encoded url as /test-get/email=a#b.com ? or /test-get/a%40b.com for your previous code.
I am using Spring Data and I am in doubt why even after declaring foreign entities as Lazy loaded they are getting eagerly loaded for this method:
findByReportingManager_IdAndAndLevel(Long reporterManagerId, Integer level)
On logs, I can see the query as:
select userhierar0_.id as id1_28_,
userhierar0_.LEVEL as LEVEL5_28_,
userhierar0_.REPORTING_MANAGER_ID as REPORTIN9_28_,
userhierar0_.USER_ID as USER_ID10_28_
from USER_HIERARCHY userhierar0_
left outer join
USER_V3 user1_ on userhierar0_.REPORTING_MANAGER_ID=user1_.id
where user1_.id=? and userhierar0_.CUSTOMER_ID=? and userhierar0_.LEVEL=?
why extra join even if I am passing reporting manager id ?
UserHierarchy Class:
#Entity
#Table(name = "USER_HIERARCHY")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserHierarchy {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY) // LAZY LOADING
#JoinColumn(name = "USER_ID",referencedColumnName = "ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY) //LAZY LOADING
#JoinColumn(name = "REPORTING_MANAGER_ID", referencedColumnName = "ID")
private User reportingManager;
#Column(name = "LEVEL")
private Integer level;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getReportingManager() {
return reportingManager;
}
public void setReportingManager(User reportingManager) {
this.reportingManager = reportingManager;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
#Override
public String toString() {
return ReflectionToStringBuilder.toStringExclude(this, "user", "reportingManager");
}
User Entity class
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
private Long id;
#Column(name = "EMAIL")
private String email;
#Column(name = "CUSTOMER_ID")
private Long customerId;
#Column(name = "STATUS")
private String status;
// Getter and Setter
As per Spring's doc:
At query creation time you already make sure that the parsed property
is a property of the managed domain class.
So does that mean in order to make User object in "managed state" it uses join or I am wrong in the implementation ?
I stumbled across the same problem recently and it seems that there is no solution for this at the moment in Spring Data.
However I've created a ticket for it.
If you go with Criteria API or JPQL for this particular query, then it will work properly.