I am trying to implement a subscription-model application that upon startup checks whether the record is expired or not (based on the current date) and eventually updates the "enabled" boolean value.
The Model:
#Entity
#Table(name="users")
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(unique = true)
private String name;
#NotNull
#DateTimeFormat(pattern = "dd/MM/yyyy")
private LocalDate expiry_date;
#NotNull
private boolean enabled;
//Constructor, getter, setters
}
Inside the UserService class I implemented the following method:
//Disable records if past expiration Date
public void updateRecordsBasedOnTime(){
List<User> orgs = (List<User>) userRepository.findAll();
LocalDate today = LocalDate.now();
for (User usr: users) {
if(today.isAfter(org.getExpiry_date())){
org.setEnabled(false);
}
}
}
Now, the question is, where would be appropriate calling this method? At first I though inside the #Controller class, since an instance of the userRepository is already used, but the following method doesn't work:
#PostLoad
private void updateStatus() {
userService.updateRecordsBasedOnTime();
}
Alternatively I though about implementing a cron method that would run though the records once every 12h:
#Scheduled(cron = "0 */12 * * *")
public static void updateDatabase() {
}
but even if I manage to successfully implement this method, I won't have the data updated at startup. Could anyone shed some light on this and maybe provide me with a better approach?
I would recommend you to use triggers. With the help of triggers, you can not only execute tasks on startup but also at a periodic interval. you can read more about that and what suits your particular need.
https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/html/scheduling.html
Related
In my application I am using projections to map *Entity objects to simplified or modified versions of the actual record in the database.
However, I have a particular use case where I am required to replace a certain value from one of the nested projections. Since these are interfaces and also get proxied by Spring, I am not sure if what I want is actually possible but to bring it down to one very simpel example:
Assume I have a UserEntity and a User projection. For my User projection I can simply execute:
User user = this.userEntityRepository.findById(userId);
However, if I want to change something, I am not sure if that is possible. Namely, I cannot do something like this:
if (user.getAge() < 18) {
user.setDisplayName(null);
}
Now, I am aware that I could create an anonymous class new User() { .. } and just pass in the values I required but in my case the objects are nested and hence this is not an option.
The question
Is there another way to replace a value, e.g. displayName as above, without using an anonymous class?
Elaborative example
Reading the following is not really necessary but in order to illustrate my issue in more detail I have pseudo-coded an example that shows a bit closer what the problem is in my particular case.
We have a simple UserEntity:
#Entity
#Table(name = "app_user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#Column
private Integer age;
// Setter & Getter ..
}
#Entity
#Table(name = "event")
public class EventEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "event")
private List<EventAttendeeEntity> attendees;
// ..
}
We have a table which maps users to events:
#Entity
#Table(name = "attendee")
public class AttendeeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private EventEntity event;
#ManyToOne
private UserEntity user;
// ..
}
Further, we have projections for these entities which we prepare as views for our clients:
/*
* Projection for User
*/
public interface User {
// All the properties ..
}
/*
* Projection for AttendeeEntity
*/
public interface Attendee {
Long getId();
User getUser();
}
/*
* Projection for EventEntity
*/
public interface Event {
Long getId();
String getName();
List<Attendee> getAttendees();
}
In one of the services we fetch UserEvent. Here, let's say, we want to remove the names of all users below 18 and still return userEvent we just fetched.
public Event getEvent(Long id, Boolean anonymize) {
Event event = this.eventRepository.findById(id);
// The "anonymize" is to highlight that I cannot
// simply solve this in a User-projection
if (!anonymize) {
return event;
}
event
.getAttendees();
.stream()
.peek(attendee -> {
User user = attendee.getUser();
if(user.getAge() < 18) {
// Here we create a new user object without a name
User newUser = new User() {
#Override
String getDisplayName() { return null; }
#Override
Integer getAge() { return user.getAge(); }
}
// !! This is where we hit the problem since we cannot
// !! replace the old user object like this
attendee.setUser(newUser);
}
});
return event;
}
One solution is to use SPEL in you projection selector. Please try
public interface Attendee {
Long getId();
#Value("#{target.user.age >= 18 ? target.user : new your.package.UserEntity()}")
User getUser();
}
Replace you.package with the package of UserEntity. Pay attention to put new UserEntity() and not new User(). This way an empty model will be projected as an empty interface User.
You can't use projections to update your code.
As a final note, it's important to remember that projections and excerpts are meant for the read-only purpose.
API Data Rest Projections Baeldung
I'm creating a test and basically doing different transactions inside a #Transactional method.
I add a Project, then add a Task to it, and last will fetch the project again from DB to test it has the task saved.
Please note the case I'm showing is a unit test but I'm interested in fixing the transactional methods and not the test itself as I already had this in the past in "production code".
Model Classes:
#Entity
#Table(name = "Task")
public class Task {
#Id
#SequenceGenerator(name = "TaskSeq", sequenceName = "TaskSeq", initialValue = 100)
#GeneratedValue(generator = "TaskSeq")
private Long id;
#Column(nullable = false)
private String name;
private String description;
private LocalDateTime inZ;
private LocalDateTime outZ;
private boolean completed;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
#JoinColumn(name = "projectId")
private Project project;
}
#Entity
#Table(name = "Project")
public class Project {
#Id
#SequenceGenerator(name = "ProjectSeq", sequenceName = "ProjectSeq", initialValue = 100)
#GeneratedValue(generator = "ProjectSeq")
private Long id;
#Column(nullable = false)
private String name;
#OneToMany(mappedBy = "project", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
private List<Task> tasks;
}
Service Classes:
#Service
public class ProjectServiceImpl implements ProjectService {
private final ProjectRepository projectRepository;
#Autowired
public ProjectServiceImpl(ProjectRepository projectRepository) {
this.projectRepository = projectRepository;
}
#Override
public Project save(Project project) {
return projectRepository.save(project);
}
#Override
public List<Project> findAll() {
return projectRepository.findAll();
}
}
#Service
public class TaskServiceImpl implements TaskService {
private TaskRepository taskRepository;
private ProjectRepository projectRepository;
#Autowired
public TaskServiceImpl(TaskRepository taskRepository, ProjectRepository projectRepository) {
this.taskRepository = taskRepository;
this.projectRepository = projectRepository;
}
#Override
#Transactional
public Task addTaskToProject(Long id, Task task) {
Project project = projectRepository.findById(id).orElseThrow(() -> new RuntimeException());
task.setProject(project);
return taskRepository.save(task);
}
}
The class I'm trying to use the transactional method:
public class TaskServiceTest extends JavaExampleApplicationTests {
#Autowired
private ProjectService projectService;
#Autowired
private TaskService taskService;
#Test
// #Transactional
public void canInsertTaskToProject() {
Project project = new Project();
project.setName("create company");
project = projectService.save(project);
Task task = new Task();
task.setName("Check how many people we need to hire");
task = taskService.addTaskToProject(project.getId(), task);
assertTrue(task.getId() > 0);
List<Project> projects = projectService.findAll();
assertEquals(1, projects.size());
assertEquals(1, projects.get(0).getTasks().size());
assertEquals(task.getId(), projects.get(0).getTasks().get(0).getId());
}
}
If I add a #Transactional(REQUIRES_NEW) to the methods in the service it will work, but I don't want it as if this method is called inside a real transaction I want it to be rolled back accordingly. Also I'd like to avoid using too many REQUIRES_NEW to avoid future problems
If I remove the #Transactional from the test method, it won't work when I test the size of the task list on last two lines as they are lazy.
If I add the #Transactional on the test method, it will throw me NullPointerException on the two last lines as when I do a projectService.findAll() the tasks are not commited yet
What is the best way to make it work ? I thought that inside a #Transactional when I used another command from db it would get the latest updates that were not committed yet..
Thanks in advance!
Update: added the reason I removed the #Transactional from test
In its roots this is a design issue. In the test code you're making changes and then verifying that those changes are made. This brings us to the problem of #Transactional or not.
With #Transactional, you end up in the situation where the changes are made, but they're not flushed or committed yet, so you can't see the changes in the same transaction. In this case you would either need to flush the results, and/or reconsider your transaction boundaries.
Without #Transactional, the individual transactions work fine, but since you're not inside a transaction, you can't initialize the lazy-loaded entities. For this your option is to either load the values in a way that eagerly initializes those, or load the values in a way that doesn't require initialization. Both of those would probably involve custom queries in your repository.
Without seeing the actual codebase, it's impossible to say what would be the optimal way to go. Using saveAndFlush() will probably solve the problem in this case, but it's not much of a general solution.
First of all, I have to state that I have 3 databases enabled in my project.
Any method about insertion to the database doesn't seem to apply, but the select methods do. That's the weird thing, it isn't that Spring Boot cannot determine which database to use because the same repository selects from the correct database but cannot insert. In addition, I get no errors, in Java environment neither in MySQL ( I enabled debug options on application.properties )
To summarize, save method doesn't insert to the database but the same repository selects without any issues. I checked if I have privileges for insertion into the database and I do (I also added them again just in case).
I am using the same entity for both selection and insertion.
The table I want to access is named log and the database db1. In addition, insertion works on db3.
Also, I have configured DataSource for all three databases.
I would like to add, that there are multiple tables on the three databases that have the same name. For other reasons, I can't give you the exact naming, but I would try of course any suggestions about naming. But I have to say, on all three databases the selection happens exactly as wanted.
application.properties
server.port=8086
db1.datasource.test-on-borrow=true
db1.datasource.validation-query=SELECT 1
db2.datasource.test-on-borrow=true
db2.datasource.validation-query=SELECT 1
db3.datasource.test-on-borrow=true
db3.datasource.validation-query=SELECT 1
spring.jpa.hibernate.ddl-auto=validate
jwt.header=Authorization
jwt.secret= //mysecret
jwt.expiration=14400
jwt.route.authentication.path=/login
jwt.route.authentication.refresh=/refresh
spring.profiles.active=prod
webapp.cors.allowedOrigins= //list of allowed origins
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
application-prod.properties
server.port=8086
db1.datasource.url= //db1 url
db1.datasource.username= //username
db1.datasource.password= //password
db1.datasource.driverClassName=com.mysql.jdbc.Driver
db1.datasource.test-on-borrow=true
db1.datasource.validation-query=SELECT 1
db2.datasource.url= //db2 url
db2.datasource.username= //username
db2.datasource.password= //password
db2.datasource.driverClassName=com.mysql.jdbc.Driver
db2.datasource.test-on-borrow=true
db2.datasource.validation-query=SELECT 1
db3.datasource.url= //db3 url
db3.datasource.username= //username
db3.datasource.password= //password
db3.datasource.driverClassName=com.mysql.jdbc.Driver
db3.datasource.test-on-borrow=true
db3.datasource.validation-query=SELECT 1
Log JPA entity , log is the name of the table in database db1
#Entity
#Table(name = "log" , catalog = "db1")
public class Log implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long logID;
private Integer uploadSeq;
private String date;
public Log() {
}
public Log(Integer uploadSeq, String date) {
this.uploadSeq = uploadSeq;
this.date = date;
}
#Column(name = "logID", unique = true, nullable = false)
public Long getLogID() {
return logID;
}
public void setLogID(Long logID) {
this.logID = logID;
}
#Column(name = "uploadSeq", nullable = false)
public Integer getUploadSeq() {
return uploadSeq;
}
public void UploadSeq(Integer uploadSeq) {
this.uploadSeq = uploadSeq;
}
#Column(name = "date", nullable = false)
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
Db1LogRepository repository for log table
public interface Db1LogRepository extends JpaRepository<Log,Long> {
public Log findFirstByOrderByLogIDDesc(); //is being used on another part of the project
}
Db1LogComponent component for accessing the repository
#Component
public class Db1LogComponent {
#Autowired
Db1logRepository db1LogRepository;
public void addDate(Log log) {
System.out.println(db1LogRepository.findAll().size()); //Retrieves the correct entities of the table log in db1
db1LogRepository.save(log); //Doesn't save to the database
}
}
Edit: DB3 has #Primary annotation on config file, where the other two configs about the other two databases don't.
Ensure that you are using #Repository
#Transactional annotations on top of repository like below.
#Repository
#Transactional
public interface Db1LogRepository extends JpaRepository<Log,Long>
Let's assume we have a database table (a JPA entity), which is created as follows
id|start_date |end_date |object_id
1 |2018-01-01 00:00:00|2018-07-01 20:00:00|1
I would like to run a specific method when end_date passes current_date() - and I want Spring/Java/DB to start that method, without using schedulers. Some pseudo code below:
when (end_date <= current_date()) -> myClass.executeMyMethod(object_id)
Is this even possible? I've searched through a lot of different sites and am yet to find an answer.
Edit - here is an entity for which I would like to do that:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "time_based_event", schema = "timing")
public class TimePassingByTrigger {
#Column(name = "id", nullable = false)
protected Long id;
#Column(name = "start_date", nullable = false)
protected LocalDateTime startDate;
#Column(name = "endDate", nullable = false)
protected LocalDateTime endDate;
#Column(name = "object_id", nullable = false)
protected Long objectId;
}
And for service call - let's assume that I would like to do something like:
public class TimeFiredEvent {
// this method should be executed for each objectId for
// which end_date has passed
public void thisShouldRunOnlyWhenDatePasses(Long objectId) {
System.out.println("Time has passed for object: " + objectId);
}
}
I think what you may want is an EntityListener
#Table(name="time_based_event", schema="timing")
#EntityListeners(class=TimeFiredEvent.class)
public class TimePassingByTrigger {
....
And you define the actual listener
public class TimeFiredEvent {
#PostPersist
public void checkTriggerTime(TimePassingByTrigger trig) {
if(trig.getEndDate().before(new Date()) {
thisShouldRunOnlyWhenDatePasses(trig.getId());
}
}
public void thisShouldRunOnlyWhenDatePasses(Long objectId) {
}
}
However, this is probably not what you actually need. An EntityListener is only going to execute when the entity is saved, retrieved, or deleted. (The valid callback points are #PrePersist, #PreRemove, #PostPersist, #PostRemove, #PreUpdate, #PostUpdate, #PostLoad)
But there is no mechanism for executing code just because time has continued its inexorable march forward. You will still have to have some kind of polling mechanism or a sleeping Java thread if you want to do a check at other times.
I have 2 entities in my DB with one-to-one one directional mapping:
User and PasswordResetToken. The idea behind this is to create new token each time user requests password reset and store only the latest one.
Below are my entities:
#Entity
#Table(name = "USERS")
#Getter #Setter
public class User implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq")
#SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#Column(name = "ROLE")
private Integer role;
}
///...
#Entity
#Table(name = "PASSWORD_RESET_TOKENS")
#Getter
#Setter
public class PasswordResetToken implements Serializable {
private static final int EXPIRATION = 24;
#Column(name = "TOKEN")
private String token;
#Id
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(nullable = false, name = "user_id")
private User user;
#Column(name = "EXPIRY_DATE")
private Instant expiryDate;
public PasswordResetToken() {
}
public void setExpiryDate(ZonedDateTime expiryDate) {
this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant();
}
}
Also, I have DTOs created for both of them to pass them around my app.
Code snippets:
#Getter #Setter
public class PasswordResetTokenModel {
private String token;
private ZonedDateTime expiryDate;
private UserModel user;
}
UserModel is also used for Spring Security
#Getter
#Setter
public class UserModel extends User {
public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private long id;
private String name;
public String getEmail() {
return this.getUsername();
}
}
For population I've created 2 populators:
#Component
public class UserPopulatorImpl implements UserPopulator {
#Autowired
UserDetailsService userDetailsService;
#Override
public UserModel populateToDTO(User user) {
UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole()));
userModel.setId(user.getId());
return userModel;
}
#Override
public User populateToDAO(UserModel userModel) {
User user = new User();
user.setEmail(userModel.getEmail());
user.setName(userModel.getName());
user.setPassword(userModel.getPassword());
//TODO: change it!
user.setRole(1);
return user;
}
}
//...
#Component
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator {
#Autowired
UserPopulator userPopulator;
#Override
public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) {
PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel();
passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser()));
passwordResetTokenModel.setToken(passwordResetToken.getToken());
passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault()));
return passwordResetTokenModel;
}
#Override
public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) {
PasswordResetToken passwordResetToken = new PasswordResetToken();
passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate());
passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser()));
passwordResetToken.setToken(passwordResetTokenModel.getToken());
return passwordResetToken;
}
}
I'm saving object using
sessionFactory.getCurrentSession().saveOrUpdate(token);
When I use this code, I'm getting following exception
object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User
There are currently 2 issues in this code:
Seems like Cascade.ALL in my OneToOne mapping is not working. If
I create separate primary key in Token class everything works almost
as expected but storing every created token in DB (more like
OneToMany relation), however I want to avoid it as I need to store
only one token per user in my DB
I don't like using new in populators, as it forces hibernate to create new object while flushing session. However, I also don't want to do another select to fetch this data from DB because just before mentioned populator I already do this query to fetch it and I think that it's an overhead.
Also, I really want to have DTOs and I don't want to remove DTO layer.
So, my questions:
What is the correct way to handle population between DTO and entities?
Are there any other improvements (probably architectural) to my solution?
Thanks a lot.
I'm not sure why you would let UserModel extend User, but I guess you did that because you didn't want to have to copy all properties from User into UserModel. Too bad, because that's what is going to be needed to have a clean separation between the entity model and data transfer model.
You get that exception because you try to persist a PasswordResetToken that has a reference to a User object with an id, but the User isn't associated with the current session. You don't have to query the user, but at least association it with the session like this:
PasswordResetToken token = // wherever you get that from
Session s = sessionFactory.getCurrentSession();
token.setUser(s.load(User.class, token.getUser().getId());
s.persist(token);
Cascading would cause the User to be created/inserted or updated via a SQL INSERT or UPDATE statement which is apparently not what you want.
You could do the Session.load() call in you populators if you want, but I'd not do that. Actually I would recommend not having populators at all, but instead create the entity objects in your service instead.
Normally you only have a few(mostly 1) ways of actually creating a new entity object, so the full extent of the transformation from DTO to entity will only be relevant in very few cases.
Most of the time you are going to do an update and for that, you should first select the existing entity and apply the fields that are allowed to be changed from the DTO on the entity object.
For providing the presentation layer with DTOs I would recommend using Blaze-Persistence Entity Views to avoid the manual mapping boilerplate and also improve performance of select queries.