From 5fb8b89d92b3de4b0a3bc14db3d41c290bbde218 Mon Sep 17 00:00:00 2001 From: SattarovAA Date: Wed, 9 Apr 2025 17:25:46 +0300 Subject: [PATCH 1/2] only hibernate update --- pom.xml | 17 +- .../aop/{ => logger}/LazyLogger.java | 2 +- .../aop/{ => logger}/LazyLoggerAdvice.java | 2 +- .../springtodo/aop/session/LazySession.java | 17 + .../aop/session/LazySessionAdvice.java | 56 +++ .../emobile/springtodo/config/AppConfig.java | 37 -- .../springtodo/config/HibernateConfig.java | 50 +++ .../springtodo/controller/AppController.java | 2 +- .../springtodo/controller/AuthController.java | 2 +- .../springtodo/controller/TaskController.java | 2 +- .../springtodo/controller/UserController.java | 2 +- .../handler/ExceptionHandlerController.java | 17 + .../emobile/springtodo/mapper/TaskMapper.java | 4 +- .../emobile/springtodo/model/entity/Task.java | 35 +- .../emobile/springtodo/model/entity/User.java | 36 ++ .../springtodo/model/util/PageInfo.java | 2 +- .../springtodo/repository/CrudRepository.java | 8 +- .../springtodo/repository/TaskRepository.java | 4 +- .../repository/impl/TaskRepositoryImpl.java | 191 ++------- .../repository/impl/UserRepositoryImpl.java | 365 ++++-------------- .../service/impl/TaskServiceImpl.java | 38 +- .../service/impl/UserServiceImpl.java | 42 +- .../impl/security/SecurityServiceImpl.java | 2 +- .../impl/security/UserDetailsServiceImpl.java | 2 +- .../com/emobile/springtodo/util/JdbcUtil.java | 26 -- .../emobile/springtodo/util/SessionUtil.java | 30 ++ src/main/resources/config/application.yml | 3 + .../SpringToDoApplicationTests.java | 31 ++ .../controller/AuthControllerTest.java | 11 + .../controller/TaskControllerTest.java | 176 ++++----- .../controller/UserControllerTest.java | 133 ++++--- .../impl/TaskRepositoryImplTest.java | 207 +++++----- .../impl/UserRepositoryImplTest.java | 209 ++++++---- .../service/impl/TaskServiceImplTest.java | 176 ++++++--- .../service/impl/UserServiceImplTest.java | 20 +- 35 files changed, 962 insertions(+), 995 deletions(-) rename src/main/java/com/emobile/springtodo/aop/{ => logger}/LazyLogger.java (85%) rename src/main/java/com/emobile/springtodo/aop/{ => logger}/LazyLoggerAdvice.java (98%) create mode 100644 src/main/java/com/emobile/springtodo/aop/session/LazySession.java create mode 100644 src/main/java/com/emobile/springtodo/aop/session/LazySessionAdvice.java create mode 100644 src/main/java/com/emobile/springtodo/config/HibernateConfig.java delete mode 100644 src/main/java/com/emobile/springtodo/util/JdbcUtil.java create mode 100644 src/main/java/com/emobile/springtodo/util/SessionUtil.java diff --git a/pom.xml b/pom.xml index e823d390..5b7359a3 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ com.emobile - SpringToDo + SpringToDo-hib 0.0.1-SNAPSHOT SpringToDo SpringToDo @@ -40,11 +40,20 @@ org.springframework.boot spring-boot-starter-security - - org.springframework.boot - spring-boot-starter-jdbc + org.hibernate + hibernate-core + 6.6.1.Final + + + + + + + + + org.postgresql postgresql diff --git a/src/main/java/com/emobile/springtodo/aop/LazyLogger.java b/src/main/java/com/emobile/springtodo/aop/logger/LazyLogger.java similarity index 85% rename from src/main/java/com/emobile/springtodo/aop/LazyLogger.java rename to src/main/java/com/emobile/springtodo/aop/logger/LazyLogger.java index 4f5e8a6d..56039f72 100644 --- a/src/main/java/com/emobile/springtodo/aop/LazyLogger.java +++ b/src/main/java/com/emobile/springtodo/aop/logger/LazyLogger.java @@ -1,4 +1,4 @@ -package com.emobile.springtodo.aop; +package com.emobile.springtodo.aop.logger; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/com/emobile/springtodo/aop/LazyLoggerAdvice.java b/src/main/java/com/emobile/springtodo/aop/logger/LazyLoggerAdvice.java similarity index 98% rename from src/main/java/com/emobile/springtodo/aop/LazyLoggerAdvice.java rename to src/main/java/com/emobile/springtodo/aop/logger/LazyLoggerAdvice.java index c1e94884..e98d50f5 100644 --- a/src/main/java/com/emobile/springtodo/aop/LazyLoggerAdvice.java +++ b/src/main/java/com/emobile/springtodo/aop/logger/LazyLoggerAdvice.java @@ -1,4 +1,4 @@ -package com.emobile.springtodo.aop; +package com.emobile.springtodo.aop.logger; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; diff --git a/src/main/java/com/emobile/springtodo/aop/session/LazySession.java b/src/main/java/com/emobile/springtodo/aop/session/LazySession.java new file mode 100644 index 00000000..587b6505 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/aop/session/LazySession.java @@ -0,0 +1,17 @@ +package com.emobile.springtodo.aop.session; + +import com.emobile.springtodo.model.util.SessionAction; +import com.emobile.springtodo.model.util.TransactionalType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface LazySession { + TransactionalType type() default TransactionalType.IGNORE; + + SessionAction action() default SessionAction.CLOSE; +} diff --git a/src/main/java/com/emobile/springtodo/aop/session/LazySessionAdvice.java b/src/main/java/com/emobile/springtodo/aop/session/LazySessionAdvice.java new file mode 100644 index 00000000..457b3d0b --- /dev/null +++ b/src/main/java/com/emobile/springtodo/aop/session/LazySessionAdvice.java @@ -0,0 +1,56 @@ +package com.emobile.springtodo.aop.session; + +import com.emobile.springtodo.model.util.SessionAction; +import com.emobile.springtodo.model.util.TransactionalType; +import com.emobile.springtodo.util.SessionUtil; +import lombok.RequiredArgsConstructor; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.hibernate.Transaction; +import org.hibernate.resource.transaction.spi.TransactionStatus; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Aspect +@Component +public class LazySessionAdvice { + private final SessionUtil sessionUtil; + + @Before(value = "@annotation(param)") + public void beforeLazySession(LazySession param) { + if (param.type() == TransactionalType.AROUND) { + sessionUtil.getSession().beginTransaction(); + } + } + + @AfterThrowing(value = "@annotation(LazySession)") + public void afterThrowingLazySession(JoinPoint joinPoint) { + Transaction transaction = sessionUtil.getSession().getTransaction(); + if (transaction != null) { + transaction.rollback(); + } + sessionUtil.closeSession(); + } + + @AfterReturning(value = "@annotation(param)") + public void afterReturnLazySession(LazySession param) { + if (param.type() == TransactionalType.AROUND) { + sessionUtil.getSession().getTransaction().commit(); + } + if (param.action() == SessionAction.CLOSE){ + sessionUtil.closeSession(); + } + if (param.action() == SessionAction.CHECK){ + boolean oneOf = sessionUtil.getSession() + .getTransaction() + .getStatus() + .isOneOf(TransactionStatus.NOT_ACTIVE); + if (oneOf){ + sessionUtil.closeSession(); + } + } + } +} diff --git a/src/main/java/com/emobile/springtodo/config/AppConfig.java b/src/main/java/com/emobile/springtodo/config/AppConfig.java index d5f1ebc2..a12682fd 100644 --- a/src/main/java/com/emobile/springtodo/config/AppConfig.java +++ b/src/main/java/com/emobile/springtodo/config/AppConfig.java @@ -1,20 +1,14 @@ package com.emobile.springtodo.config; import com.emobile.springtodo.config.property.cache.AppCacheProperties; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.jdbc.datasource.DriverManagerDataSource; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import javax.sql.DataSource; import java.time.Clock; /** @@ -27,37 +21,6 @@ @EnableConfigurationProperties(AppCacheProperties.class) @PropertySource("classpath:config/application.yml") public class AppConfig { - @Value("${spring.datasource.driver-class-name}") - private String dataSourceDriverClassName; - @Value("${spring.datasource.url}") - private String dataSourceUrl; - @Value("${spring.datasource.username}") - private String dataSourceUsername; - @Value("${spring.datasource.password}") - private String dataSourcePassword; - - @Bean - public DataSource dataSource() { - DriverManagerDataSource dataSource = new DriverManagerDataSource(); - - dataSource.setDriverClassName(dataSourceDriverClassName); - dataSource.setUrl(dataSourceUrl); - dataSource.setUsername(dataSourceUsername); - dataSource.setPassword(dataSourcePassword); - - return dataSource; - } - - @Bean - public JdbcTemplate jdbcTemplate(DataSource dataSource) { - return new JdbcTemplate(dataSource); - } - - @Bean - public PlatformTransactionManager txManager() { - return new DataSourceTransactionManager(dataSource()); - } - @Bean public Clock clock() { return Clock.systemDefaultZone(); diff --git a/src/main/java/com/emobile/springtodo/config/HibernateConfig.java b/src/main/java/com/emobile/springtodo/config/HibernateConfig.java new file mode 100644 index 00000000..b606790e --- /dev/null +++ b/src/main/java/com/emobile/springtodo/config/HibernateConfig.java @@ -0,0 +1,50 @@ +package com.emobile.springtodo.config; + +import com.emobile.springtodo.model.entity.Task; +import com.emobile.springtodo.model.entity.User; +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HibernateConfig { + @Value("${spring.datasource.driver-class-name}") + private String dataSourceDriverClassName; + @Value("${spring.datasource.url}") + private String dataSourceUrl; + @Value("${spring.datasource.username}") + private String dataSourceUsername; + @Value("${spring.datasource.password}") + private String dataSourcePassword; + + @Bean + public SessionFactory sessionFactory() { + return new org.hibernate.cfg.Configuration() + .setProperty("hibernate.connection.driver_class", dataSourceDriverClassName) + .setProperty("hibernate.connection.url", dataSourceUrl) + .setProperty("hibernate.connection.username", dataSourceUsername) + .setProperty("hibernate.connection.password", dataSourcePassword) + .setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect") + .setProperty("hibernate.show_sql", "true") + .addAnnotatedClass(User.class) + .addAnnotatedClass(Task.class) + .buildSessionFactory(); + } + + + +// @Bean +// public PlatformTransactionManager transactionManager() { +// JpaTransactionManager transactionManager = new JpaTransactionManager(); +// transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); +// return transactionManager; +// } + +// @Bean +// public HibernateTransactionManager transactionManager() { +// HibernateTransactionManager txManager = new HibernateTransactionManager(); +// txManager.setSessionFactory(sessionFactory().getObject()); +// return txManager; +// } +} diff --git a/src/main/java/com/emobile/springtodo/controller/AppController.java b/src/main/java/com/emobile/springtodo/controller/AppController.java index aada94d0..d56fd369 100644 --- a/src/main/java/com/emobile/springtodo/controller/AppController.java +++ b/src/main/java/com/emobile/springtodo/controller/AppController.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.controller; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; diff --git a/src/main/java/com/emobile/springtodo/controller/AuthController.java b/src/main/java/com/emobile/springtodo/controller/AuthController.java index 085dcef4..55a1f01a 100644 --- a/src/main/java/com/emobile/springtodo/controller/AuthController.java +++ b/src/main/java/com/emobile/springtodo/controller/AuthController.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.controller; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.mapper.UserMapper; import com.emobile.springtodo.model.dto.user.UserSaveRequest; import com.emobile.springtodo.model.dto.util.SimpleResponse; diff --git a/src/main/java/com/emobile/springtodo/controller/TaskController.java b/src/main/java/com/emobile/springtodo/controller/TaskController.java index 7a16fc42..af8a087f 100644 --- a/src/main/java/com/emobile/springtodo/controller/TaskController.java +++ b/src/main/java/com/emobile/springtodo/controller/TaskController.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.controller; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.mapper.TaskMapper; import com.emobile.springtodo.model.dto.task.TaskInsertRequest; import com.emobile.springtodo.model.dto.task.TaskListResponse; diff --git a/src/main/java/com/emobile/springtodo/controller/UserController.java b/src/main/java/com/emobile/springtodo/controller/UserController.java index 87c29f0a..909bfd6f 100644 --- a/src/main/java/com/emobile/springtodo/controller/UserController.java +++ b/src/main/java/com/emobile/springtodo/controller/UserController.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.controller; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.mapper.UserMapper; import com.emobile.springtodo.model.dto.user.UserInsertRequest; import com.emobile.springtodo.model.dto.user.UserListResponse; diff --git a/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java b/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java index 01d93a9c..74473bc7 100644 --- a/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java +++ b/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java @@ -4,6 +4,7 @@ import com.emobile.springtodo.exception.DeleteEntityWithReferenceException; import com.emobile.springtodo.exception.EntityNotFoundException; import lombok.extern.slf4j.Slf4j; +import org.hibernate.TransactionException; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -78,6 +79,22 @@ public ResponseEntity badRequest( return buildResponse(HttpStatus.BAD_REQUEST, ex, webRequest); } + /** + * ExceptionHandler for {@link AlreadyExitsException}. + * + * @param ex exception type of {@link AlreadyExitsException}. + * @param webRequest web request for exception description. + * @return {@link ResponseEntity} with {@link ErrorResponseBody}. + * @see #buildResponse(HttpStatus, Exception, WebRequest) + */ + @ExceptionHandler(TransactionException.class) + public ResponseEntity badRequest( + TransactionException ex, + WebRequest webRequest + ) { + return buildResponse(HttpStatus.BAD_REQUEST, ex, webRequest); + } + /** * ExceptionHandler for {@link EntityNotFoundException}. * diff --git a/src/main/java/com/emobile/springtodo/mapper/TaskMapper.java b/src/main/java/com/emobile/springtodo/mapper/TaskMapper.java index 84977410..ccc8fa9a 100644 --- a/src/main/java/com/emobile/springtodo/mapper/TaskMapper.java +++ b/src/main/java/com/emobile/springtodo/mapper/TaskMapper.java @@ -6,6 +6,7 @@ import com.emobile.springtodo.model.dto.task.TaskSaveRequest; import com.emobile.springtodo.model.entity.Task; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.Named; import org.mapstruct.ReportingPolicy; @@ -45,8 +46,7 @@ public interface TaskMapper { * @param model {@link Task} for mapping. * @return mapped {@link TaskResponse}. */ -// @Mapping(target = "createdAt", qualifiedByName = "OffsetDateTimeToString") -// @Mapping(target = "updatedAt", qualifiedByName = "OffsetDateTimeToString") + @Mapping(source = "author.id", target = "authorId") TaskResponse modelToResponse(Task model); diff --git a/src/main/java/com/emobile/springtodo/model/entity/Task.java b/src/main/java/com/emobile/springtodo/model/entity/Task.java index 88ad60b8..46f34de1 100644 --- a/src/main/java/com/emobile/springtodo/model/entity/Task.java +++ b/src/main/java/com/emobile/springtodo/model/entity/Task.java @@ -1,11 +1,7 @@ package com.emobile.springtodo.model.entity; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; +import jakarta.persistence.*; +import lombok.*; import lombok.experimental.FieldNameConstants; import java.io.Serial; @@ -22,35 +18,58 @@ @Getter @FieldNameConstants @Builder +//@NamedEntityGraph( +// name = "Task.withAuthor", +// attributeNodes = @NamedAttributeNode("author") +//) +@Entity +@Table(name = "tasks") public class Task implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * Long Task id. */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = Fields.id) private Long id; /** * Task name. */ + @Column(name = Fields.name) private String name; /** * Task description. */ + @Column(name = Fields.description) private String description; /** * Task {@link TaskStatus} status. */ + @Enumerated(EnumType.STRING) + @Column(name = Fields.status) private TaskStatus status; /** * Task creation time without timezone. */ + @Column(name = "created_at") private LocalDateTime createdAt; /** * Task update time without timezone. */ + @Column(name = "updated_at") private LocalDateTime updatedAt; /** - * The User id who owns the Task. + * The User who owns the Task. */ - private Long authorId; + @EqualsAndHashCode.Exclude + @ManyToOne + @JoinColumn(name = "author_id", referencedColumnName = "id") + private User author; + + @EqualsAndHashCode.Include + public Long getAuthorId() { + return author.getId(); + } } diff --git a/src/main/java/com/emobile/springtodo/model/entity/User.java b/src/main/java/com/emobile/springtodo/model/entity/User.java index 9ab52043..147541d0 100644 --- a/src/main/java/com/emobile/springtodo/model/entity/User.java +++ b/src/main/java/com/emobile/springtodo/model/entity/User.java @@ -1,11 +1,22 @@ package com.emobile.springtodo.model.entity; import com.emobile.springtodo.model.security.RoleType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldNameConstants; @@ -24,32 +35,57 @@ @Getter @FieldNameConstants @Builder +//@NamedEntityGraph( +// name = "User.withTasks", +// attributeNodes = @NamedAttributeNode("taskList") +//) +@Entity +@Table(name = "users", + indexes = { + @Index(columnList = User.Fields.username, unique = true), + @Index(columnList = User.Fields.email, unique = true) + }) public class User implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * Long User id. */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = Fields.id) private Long id; /** * User name. */ + @Column(name = Fields.username) private String username; /** * User password. */ + @Column(name = Fields.password) private String password; /** * User email. */ + @Column(name = Fields.email) private String email; /** * User authorization role type. */ + @Enumerated(EnumType.STRING) + @Column(name = Fields.role) private RoleType role; /** * User tasks. */ + @ToString.Exclude + @OneToMany(mappedBy = Task.Fields.author, orphanRemoval = true) @Builder.Default private List taskList = new ArrayList<>(); + + @ToString.Include + public String getTaskListSize() { + return String.valueOf(taskList.size()); + } } diff --git a/src/main/java/com/emobile/springtodo/model/util/PageInfo.java b/src/main/java/com/emobile/springtodo/model/util/PageInfo.java index 46f65ff0..0836f048 100644 --- a/src/main/java/com/emobile/springtodo/model/util/PageInfo.java +++ b/src/main/java/com/emobile/springtodo/model/util/PageInfo.java @@ -33,5 +33,5 @@ public record PageInfo( /** * Minimum size of the number field. */ - private static final int MIN_NUMBER_SIZE = 1; + private static final int MIN_NUMBER_SIZE = 0; } diff --git a/src/main/java/com/emobile/springtodo/repository/CrudRepository.java b/src/main/java/com/emobile/springtodo/repository/CrudRepository.java index 79cb3d45..e5c4f734 100644 --- a/src/main/java/com/emobile/springtodo/repository/CrudRepository.java +++ b/src/main/java/com/emobile/springtodo/repository/CrudRepository.java @@ -45,11 +45,9 @@ public interface CrudRepository { T update(T model); /** - * Delete object with {@code T.id} - * equals {@code id} from database. + * Delete object {@code model}. * - * @param id id of the object to be deleted - * @return {@code true} if success + * @param model object to be deleted */ - boolean deleteById(Long id); + void deleteById(T model);//TODPO rename } diff --git a/src/main/java/com/emobile/springtodo/repository/TaskRepository.java b/src/main/java/com/emobile/springtodo/repository/TaskRepository.java index a1b9b6ee..9d3e84c5 100644 --- a/src/main/java/com/emobile/springtodo/repository/TaskRepository.java +++ b/src/main/java/com/emobile/springtodo/repository/TaskRepository.java @@ -8,10 +8,8 @@ public interface TaskRepository extends CrudRepository { /** * Delete all {@link Task} objects in database. - * - * @return {@code true} if success */ - boolean deleteAll(); + void deleteAll(); /** * Delete all {@link Task} objects in database diff --git a/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java index d4821f6e..a19e246d 100644 --- a/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java @@ -1,205 +1,80 @@ package com.emobile.springtodo.repository.impl; -import com.emobile.springtodo.aop.LazyLogger; -import com.emobile.springtodo.exception.NullResultSetException; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.model.entity.Task; -import com.emobile.springtodo.model.entity.TaskStatus; +import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.util.Page; import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.TaskRepository; +import com.emobile.springtodo.util.SessionUtil; import lombok.RequiredArgsConstructor; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.hibernate.Session; +import org.hibernate.query.Query; import org.springframework.stereotype.Repository; +import java.util.Objects; import java.util.Optional; -import static com.emobile.springtodo.util.JdbcUtil.optionalExtractor; - -/** - * Repository for working with entity {@link Task}. - */ @RequiredArgsConstructor @Repository public class TaskRepositoryImpl implements TaskRepository { - /** - * For work with db. - */ - private final JdbcTemplate jdbcTemplate; + private final SessionUtil sessionUtil; - /** - * Find all {@link Task} objects from db - * with {@code pageNumber} and {@code pageSize} from {@code pageInfo}.
- * LIMIT - {@code pageSize}.
- * OFFSET - ({@code pageNumber} -1) * {@code pageSize}. - * - * @return {@link Page} with {@link Task} list. - * @see #getMapper() - */ @Override @LazyLogger - public Page findAll(PageInfo pageInfo) { - final String sql = """ - SELECT * FROM tasks - LIMIT ? OFFSET ? - """; - int beginInd = (pageInfo.pageNumber() - 1) * pageInfo.pageSize(); - return new Page<>( - jdbcTemplate.query(sql, - getMapper(), - pageInfo.pageSize(), - beginInd - ) - ); + public Page findAll(PageInfo pageinfo) { + int beginInd = pageinfo.pageNumber() * pageinfo.pageSize(); + int endInd = beginInd + pageinfo.pageSize(); + Query query = sessionUtil.getSession() + .createQuery("FROM Task", Task.class) + .setFirstResult(beginInd) + .setMaxResults(endInd); + return new Page<>(query.getResultList()); } - /** - * Search object {@link Task} in db. - * - * @param id id searched {@link Task} object - * @return {@link Optional} if exist, {@link Optional#empty()} if not - * @see #getMapper() - */ @Override @LazyLogger public Optional findById(Long id) { - final String sql = """ - SELECT * - FROM tasks - WHERE id=? - """; - return jdbcTemplate.query( - sql, - optionalExtractor(getMapper()), - id + return Optional.ofNullable( + sessionUtil.getSession().get(Task.class, id) ); } - /** - * Save object model of type {@link Task}. - * - * @param model object of type {@link Task} to save - * @return object of type {@link Task} that was saved - * @throws NullResultSetException if returning value from db is null - */ @Override @LazyLogger public Task save(Task model) { - final String sql = """ - INSERT INTO tasks(name, description, status, - created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?) - RETURNING * - """; - return jdbcTemplate.query(sql, - optionalExtractor(getMapper()), - model.getName(), - model.getDescription(), - model.getStatus().name(), - model.getCreatedAt(), - model.getUpdatedAt(), - model.getAuthorId() - ).orElseThrow(NullResultSetException.create( - "taskRepository save() query exception" - )); + sessionUtil.getSession().persist(model); + return model; } - /** - * Update object model of type {@link Task} - * by {@code T.id} value. - * - * @param model object of type {@link Task} to update - * @return object of type {@link Task} that was updated - * @throws NullResultSetException if returning value from db is null - */ @Override @LazyLogger public Task update(Task model) { - final String sql = """ - UPDATE tasks - SET name=?, description=?, status=?, updated_at=?, author_id =? - WHERE id=? - RETURNING * - """; - return jdbcTemplate.query(sql, - optionalExtractor(getMapper()), - model.getName(), - model.getDescription(), - model.getStatus().name(), - model.getUpdatedAt(), - model.getAuthorId(), - model.getId() - ).orElseThrow(NullResultSetException.create( - "taskRepository update() query exception" - )); + Session session = sessionUtil.getSession(); + if (Objects.isNull(session.find(User.class, model.getId()))) { + session.persist(model); + return model; + } + return session.merge(model); } - /** - * Delete object with {@code Task.id} - * equals {@code id} from database. - * - * @param id id of the object to be deleted - * @return {@code true} if success - */ @Override @LazyLogger - public boolean deleteById(Long id) { - final String sql = """ - DELETE - FROM tasks - WHERE id=? - """; - int result = jdbcTemplate.update(sql, id); - return result != 0; + public void deleteById(Task model) { + sessionUtil.getSession().remove(model); } - /** - * Delete all {@link Task} objects in database. - * - * @return {@code true} if success - */ @Override @LazyLogger - public boolean deleteAll() { - final String sql = "TRUNCATE tasks"; - int result = jdbcTemplate.update(sql); - return result != 0; + public void deleteAll() { + Query query = sessionUtil.getSession() + .createQuery("DELETE FROM Task", Task.class); + query.executeUpdate(); } - /** - * Delete all {@link Task} objects in database - * for user with {@code userId}. - * - * @return {@code true} if success - */ @Override @LazyLogger - public boolean deleteAllByUserId(Long userId) { - final String sql = """ - DELETE - FROM tasks - WHERE author_id=? - """; - int result = jdbcTemplate.update(sql, userId); - return result != 0; - } - - /** - * {@link Task} {@link RowMapper}. - * - * @return mapper for {@link Task} entity - */ - private RowMapper getMapper() { - return (rs, rn) -> Task.builder() - .id(rs.getLong(Task.Fields.id)) - .name(rs.getString(Task.Fields.name)) - .description(rs.getString(Task.Fields.description)) - .status(TaskStatus.valueOf(rs.getString(Task.Fields.status))) - .createdAt(rs.getTimestamp("created_at") - .toLocalDateTime()) - .updatedAt(rs.getTimestamp("updated_at") - .toLocalDateTime()) - .authorId(rs.getLong("author_id")) - .build(); + public boolean deleteAllByUserId(Long authorId) { + return false; } } diff --git a/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java index 1b8aad8d..9db5c2e1 100644 --- a/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java @@ -1,366 +1,131 @@ package com.emobile.springtodo.repository.impl; -import com.emobile.springtodo.aop.LazyLogger; -import com.emobile.springtodo.exception.NullResultSetException; -import com.emobile.springtodo.model.entity.Task; -import com.emobile.springtodo.model.entity.TaskStatus; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.model.entity.User; -import com.emobile.springtodo.model.security.RoleType; import com.emobile.springtodo.model.util.Page; import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.UserRepository; +import com.emobile.springtodo.util.SessionUtil; import lombok.RequiredArgsConstructor; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.hibernate.Session; +import org.hibernate.query.Query; import org.springframework.stereotype.Repository; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.Objects; import java.util.Optional; -import static com.emobile.springtodo.util.JdbcUtil.optionalExtractor; -/** - * Repository for working with entity {@link User}. - */ @RequiredArgsConstructor @Repository public class UserRepositoryImpl implements UserRepository { - /** - * For work with db. - */ - private final JdbcTemplate jdbcTemplate; + private final SessionUtil sessionUtil; - /** - * Find all {@link User} objects from db - * with {@code pageNumber} and {@code pageSize} from {@code pageInfo}.
- * LIMIT - {@code pageSize}.
- * OFFSET - ({@code pageNumber} -1) * {@code pageSize}. - * - * @return {@link Page} with {@link User} list. - * @see #getUserMapper() - */ @Override @LazyLogger - public Page findAll(PageInfo pageInfo) { - final String sql = """ - SELECT * FROM users - LIMIT ? OFFSET ? - """; - int beginInd = (pageInfo.pageNumber() - 1) * pageInfo.pageSize(); - return new Page<>( - jdbcTemplate.query( - sql, - getUserMapper(), - pageInfo.pageSize(), - beginInd - ) - ); + public Page findAll(PageInfo pageinfo) { + int beginInd = pageinfo.pageNumber() * pageinfo.pageSize(); + int endInd = beginInd + pageinfo.pageSize(); + Query query = sessionUtil.getSession() + .createQuery("FROM User", User.class) + .setFirstResult(beginInd) + .setMaxResults(endInd); + return new Page<>(query.getResultList()); } - /** - * Search object {@link User} in db. - * - * @param id id searched {@link User} object - * @return {@link Optional} if exist, {@link Optional#empty()} if not - * @see #getUserTaskMapper() - */ @Override @LazyLogger public Optional findById(Long id) { - final String sql = """ - SELECT u.*, t.* - FROM users u - LEFT JOIN tasks t on u.id = t.author_id - WHERE u.id= ? - """; - return jdbcTemplate.query( - sql, - optionalExtractor(getUserTaskMapper()), - id + return Optional.ofNullable( + sessionUtil.getSession().get(User.class, id) ); } - /** - * Find {@link User} - * with {@code User.username} equals {@code username}. - * - * @param username username searched {@link User} - * @return {@link Optional} if exist, {@link Optional#empty()} if not - */ @Override @LazyLogger - public Optional findByUsername(String username) { - final String sql = """ - SELECT u.*, t.* - FROM users u - LEFT JOIN tasks t on u.id = t.author_id - WHERE u.username= ? - """; - return jdbcTemplate.query( - sql, - optionalExtractor(getUserTaskMapper()), - username - ); + public User save(User model) { + sessionUtil.getSession().persist(model); + return model; } - /** - * Save object model of type {@link User}. - * - * @param model object of type {@link User} to save - * @return object of type {@link User} that was saved - * @throws NullResultSetException if returning value from db is null - */ @Override @LazyLogger - public User save(User model) { - final String sql = """ - INSERT INTO users(username, password, email, role) - VALUES (?,?,?,?) - RETURNING * - """; - return jdbcTemplate.query(sql, - optionalExtractor(getUserMapper()), - model.getUsername(), - model.getPassword(), - model.getEmail(), - model.getRole().name()) - .orElseThrow(NullResultSetException.create( - "userRepository save() query exception" - )); + public User update(User model) { + Session session = sessionUtil.getSession(); + if (Objects.isNull(session.find(User.class, model.getId()))) {//TODO toUpdate + session.persist(model); + return model; + } + return session.merge(model); } - /** - * Update object model of type {@link User} - * by {@code T.id} value. - * - * @param model object of type {@link User} to update - * @return object of type {@link User} that was updated - * @throws NullResultSetException if returning value from db is null - */ @Override @LazyLogger - public User update(User model) { - final String sql = """ - UPDATE users - SET username=?, password=?, email=?, role=? - WHERE id=? - RETURNING * - """; - return jdbcTemplate.query(sql, - optionalExtractor(getUserMapper()), - model.getUsername(), - model.getPassword(), - model.getEmail(), - model.getRole().name(), - model.getId()) - .orElseThrow(NullResultSetException.create( - "userRepository update() query exception" - )); + public void deleteById(User model) { + sessionUtil.getSession().remove(model); } - /** - * Delete object with {@code User.id} - * equals {@code id} from database. - * - * @param id id of the object to be deleted - * @return {@code true} if success - */ @Override @LazyLogger - public boolean deleteById(Long id) { - final String sql = """ - DELETE - FROM users - WHERE id=? - """; - int result = jdbcTemplate.update(sql, id); - return result != 0; + public Optional findByUsername(String username) { + Query query = sessionUtil.getSession() + .createQuery("FROM User WHERE username = :username", User.class); + query.setParameter("username", username); + return Optional.ofNullable(query.uniqueResult()); } - /** - * Check duplicate {@code username}. - * For save {@link User}. - * - * @param username username searched {@link User} - * @return {@code true} if exist, {@code false} if not - * @see #getExistsMapper() - */ @Override @LazyLogger public boolean existsByUsername(String username) { - final String sql = """ - SELECT EXISTS (SELECT * - FROM users - WHERE username = ?) - AS result - """; - return jdbcTemplate.queryForObject(sql, getExistsMapper(), username); + return existsByField(User.Fields.username, username); } - /** - * Check duplicate {@code username}. - * For update {@link User}. - * - * @param username username searched {@link User} - * @return {@code true} if exist, {@code false} if not - * @see #getExistsMapper() - */ @Override @LazyLogger public boolean existsByUsernameAndIdNot(String username, Long currentUserId) { - final String sql = """ - SELECT EXISTS (SELECT * - FROM users - WHERE username = ? - AND id != ?) - AS result - """; - return jdbcTemplate.queryForObject( - sql, - getExistsMapper(), - username, - currentUserId - ); + return existsByField(User.Fields.username, username, currentUserId); } - /** - * Check duplicate {@code email}. - * For save {@link User}. - * - * @param email username searched {@link User} - * @return {@code true} if exist, {@code false} if not - * @see #getExistsMapper() - */ @Override @LazyLogger public boolean existsByEmail(String email) { - final String sql = """ - SELECT EXISTS (SELECT * - FROM users - WHERE email = ?) - AS result - """; - return jdbcTemplate.queryForObject(sql, getExistsMapper(), email); + return existsByField(User.Fields.email, email); } - /** - * Check duplicate {@code email}. - * For update {@link User}. - * - * @param email username searched {@link User} - * @return {@code true} if exist, {@code false} if not - * @see #getExistsMapper() - */ @Override @LazyLogger public boolean existsByEmailAndIdNot(String email, Long currentUserId) { - final String sql = """ - SELECT EXISTS (SELECT * - FROM users - WHERE email = ? - AND id != ?) - AS result - """; - return jdbcTemplate.queryForObject( - sql, - getExistsMapper(), - email, - currentUserId - ); - } - - /** - * {@link User} {@link RowMapper}. - * - * @return mapper for {@link User} entity - */ - private RowMapper getUserMapper() { - return (rs, rn) -> User.builder() - .id(rs.getLong(User.Fields.id)) - .username(rs.getString(User.Fields.username)) - .password(rs.getString(User.Fields.password)) - .email(rs.getString(User.Fields.email)) - .role(RoleType.valueOf(rs.getString(User.Fields.role))) - .build(); - } - - /** - * Грустно и костыльно, но работает. - *
- * {code rs.getLong("author_id")} for wasNull() check. - * - * @return mapper for {@link User} with filled {@code taskList} fields - * @see #fillUserFields(ResultSet, List) - * @see #fillTaskFields(ResultSet) - */ - private RowMapper getUserTaskMapper() { - return (rs, rn) -> { - List taskList = new ArrayList<>(); - User user = fillUserFields(rs, taskList); - rs.getLong("author_id"); - if (!rs.wasNull()) { - do { - Task task = fillTaskFields(rs); - taskList.add(task); - } while (rs.next()); - } - return user; - }; - } - - /** - * fill {@link User} entity. - * {@code id} columnIndex - 1. - * - * @param rs resultSet for map - * @param taskList list ref - * @return {@link User} with filled fields - * @throws SQLException if resultSet get exception - */ - private User fillUserFields(ResultSet rs, List taskList) throws SQLException { - return User.builder() - .id(rs.getLong(1)) - .username(rs.getString(User.Fields.username)) - .password(rs.getString(User.Fields.password)) - .email(rs.getString(User.Fields.email)) - .role(RoleType.valueOf(rs.getString(User.Fields.role))) - .taskList(taskList) - .build(); + return existsByField(User.Fields.email, email, currentUserId); } - /** - * fill {@link Task} entity. - * {@code id} columnIndex - 6. - * - * @param rs resultSet for map - * @return {@link Task} with filled fields - * @throws SQLException if resultSet get exception - */ - private Task fillTaskFields(ResultSet rs) throws SQLException { - return Task.builder() - .id(rs.getLong(6)) - .name(rs.getString(Task.Fields.name)) - .description(rs.getString(Task.Fields.description)) - .status(TaskStatus.valueOf(rs.getString(Task.Fields.status))) - .createdAt(rs.getTimestamp("created_at") - .toLocalDateTime()) - .updatedAt(rs.getTimestamp("updated_at") - .toLocalDateTime()) - .authorId(rs.getLong("author_id")) - .build(); + private boolean existsByField(String fieldName, Object value) { + try { + Session session = sessionUtil.getSession(); + Query query = session.createQuery( + "SELECT COUNT(u.id) FROM User u WHERE u." + fieldName + " = :value", + Long.class); + query.setParameter("value", value); + Long count = query.uniqueResult(); + return count != null && count > 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } } - /** - * {@link RowMapper} for exist check. - * - * @return mapper for exist check - */ - private RowMapper getExistsMapper() { - return (resultSet, rowNum) -> - resultSet.getString("result") - .equals("t"); + private boolean existsByField(String fieldName, Object value, Long currentUserId) { + try { + Session session = sessionUtil.getSession(); + Query query = session.createQuery( + "SELECT COUNT(u.id) FROM User u WHERE u." + fieldName + " = :value " + + "and u.id != :userId", + Long.class); + query.setParameter("value", value); + query.setParameter("userId", currentUserId); + Long count = query.uniqueResult(); + return count != null && count > 0; + } catch (Exception e) { + e.printStackTrace(); + return false; + } } } diff --git a/src/main/java/com/emobile/springtodo/service/impl/TaskServiceImpl.java b/src/main/java/com/emobile/springtodo/service/impl/TaskServiceImpl.java index 71594d79..7eb7696e 100644 --- a/src/main/java/com/emobile/springtodo/service/impl/TaskServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/impl/TaskServiceImpl.java @@ -1,13 +1,18 @@ package com.emobile.springtodo.service.impl; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; +import com.emobile.springtodo.aop.session.LazySession; +import com.emobile.springtodo.model.util.SessionAction; +import com.emobile.springtodo.model.util.TransactionalType; import com.emobile.springtodo.config.property.cache.AppCacheProperties; import com.emobile.springtodo.exception.EntityNotFoundException; import com.emobile.springtodo.model.entity.Task; +import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.security.AppUserDetails; import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.TaskRepository; import com.emobile.springtodo.service.TaskService; +import com.emobile.springtodo.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; @@ -18,7 +23,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.text.MessageFormat; import java.time.Clock; @@ -42,6 +46,14 @@ public class TaskServiceImpl implements TaskService { * {@link Task} Repository. */ private final TaskRepository taskRepository; + /** + * {@link User} Service. + * To fill author field. + * + * @see #enrich(Task) + * @see #enrich(Task, Task) + */ + private final UserService userService; /** * Time management object. */ @@ -61,7 +73,7 @@ public void setSelf(TaskService self) { */ @Override @Cacheable(AppCacheProperties.Types.TASKS) - @Transactional(readOnly = true) + @LazySession @LazyLogger public List findAll(PageInfo pageInfo) { return taskRepository.findAll(pageInfo).content(); @@ -76,7 +88,7 @@ public List findAll(PageInfo pageInfo) { */ @Override @Cacheable(value = AppCacheProperties.Types.TASK_BY_ID, key = "#id") - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public Task findById(Long id) { return taskRepository.findById(id).orElseThrow( @@ -102,7 +114,7 @@ public Task findById(Long id) { @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, key = "#result.authorId"), @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public Task save(Task model) { model = enrich(model); @@ -125,7 +137,7 @@ public Task save(Task model) { @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, key = "#result.authorId"), @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public Task update(Long id, Task model) { model = enrich(model, self.findById(id)); @@ -145,7 +157,7 @@ private Task enrich(Task model) { .status(model.getStatus()) .createdAt(LocalDateTime.now(clock)) .updatedAt(LocalDateTime.now(clock)) - .authorId(getCurrentUserId()) + .author(userService.findById(getCurrentUserId())) .build(); } @@ -180,7 +192,7 @@ private Task enrich(Task model, Task taskToUpdate) { : model.getStatus()) .createdAt(taskToUpdate.getCreatedAt()) .updatedAt(LocalDateTime.now(clock)) - .authorId(taskToUpdate.getAuthorId()) + .author(userService.findById(taskToUpdate.getAuthorId())) .build(); } @@ -197,11 +209,11 @@ private Task enrich(Task model, Task taskToUpdate) { @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, allEntries = true), @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public void deleteById(Long id) { - self.findById(id); - taskRepository.deleteById(id); + Task task = self.findById(id); + taskRepository.deleteById(task); } /** @@ -215,7 +227,7 @@ public void deleteById(Long id) { @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, allEntries = true), @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public void deleteAll() { taskRepository.deleteAll(); @@ -233,7 +245,7 @@ public void deleteAll() { @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, key = "#userId"), @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public void deleteAllByUserId(Long userId) { taskRepository.deleteAllByUserId(userId); diff --git a/src/main/java/com/emobile/springtodo/service/impl/UserServiceImpl.java b/src/main/java/com/emobile/springtodo/service/impl/UserServiceImpl.java index 23687951..546ef04e 100644 --- a/src/main/java/com/emobile/springtodo/service/impl/UserServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/impl/UserServiceImpl.java @@ -1,14 +1,15 @@ package com.emobile.springtodo.service.impl; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; +import com.emobile.springtodo.aop.session.LazySession; +import com.emobile.springtodo.model.util.SessionAction; +import com.emobile.springtodo.model.util.TransactionalType; import com.emobile.springtodo.config.property.cache.AppCacheProperties; import com.emobile.springtodo.exception.AlreadyExitsException; import com.emobile.springtodo.exception.EntityNotFoundException; -import com.emobile.springtodo.model.entity.Task; import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.UserRepository; -import com.emobile.springtodo.service.TaskService; import com.emobile.springtodo.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; @@ -20,7 +21,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.text.MessageFormat; import java.util.List; @@ -42,11 +42,6 @@ public class UserServiceImpl implements UserService { * {@link User} Repository. */ private final UserRepository userRepository; - /** - * {@link Task} Service. - * To delete all task deleted user. - */ - private final TaskService taskService; /** * Default PasswordEncoder. * Needed to define and update the field password in {@link User}. @@ -70,7 +65,7 @@ public void setSelf(UserService self) { */ @Override @Cacheable(AppCacheProperties.Types.USERS) - @Transactional(readOnly = true) + @LazySession @LazyLogger public List findAll(PageInfo pageInfo) { return userRepository.findAll(pageInfo).content(); @@ -85,7 +80,7 @@ public List findAll(PageInfo pageInfo) { */ @Override @Cacheable(value = AppCacheProperties.Types.USER_BY_ID, key = "#id") - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public User findById(Long id) { return userRepository.findById(id).orElseThrow( @@ -107,7 +102,7 @@ public User findById(Long id) { */ @Override @Cacheable(value = AppCacheProperties.Types.USER_BY_NAME, key = "#username") - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public User findByUsername(String username) { return userRepository.findByUsername(username).orElseThrow( @@ -129,7 +124,7 @@ public User findByUsername(String username) { @Override @Cacheable(value = AppCacheProperties.Types.USER_BY_NAME, key = "#model.username") @CacheEvict(value = AppCacheProperties.Types.USERS, allEntries = true) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public User save(User model) { model = enrich(model); @@ -151,7 +146,7 @@ public User save(User model) { @CachePut(value = AppCacheProperties.Types.USER_BY_NAME, key = "#result.username") }) @CacheEvict(value = AppCacheProperties.Types.USERS, allEntries = true) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public User update(Long id, User model) { model = enrich(model, self.findById(id)); @@ -212,7 +207,7 @@ private User enrich(User model, User userToUpdate) { * @param username username to check. * @throws AlreadyExitsException if username already exist. */ - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public void checkDuplicateUsername(String username) { if (userRepository.existsByUsername(username)) { @@ -234,7 +229,7 @@ public void checkDuplicateUsername(String username) { * @throws AlreadyExitsException if username already exist * excluding {@link User} with currentUserId. */ - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public void checkDuplicateUsername(String username, Long currentUserId) { if (userRepository.existsByUsernameAndIdNot(username, currentUserId)) { @@ -254,7 +249,7 @@ public void checkDuplicateUsername(String username, Long currentUserId) { * @param email email to check. * @throws AlreadyExitsException if email already exist. */ - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public void checkDuplicateEmail(String email) { if (userRepository.existsByEmail(email)) { @@ -276,7 +271,7 @@ public void checkDuplicateEmail(String email) { * @throws AlreadyExitsException if email already exist * excluding {@link User} with currentUserId. */ - @Transactional(readOnly = true) + @LazySession(action = SessionAction.CHECK) @LazyLogger public void checkDuplicateEmail(String email, Long currentUserId) { if (userRepository.existsByEmailAndIdNot(email, currentUserId)) { @@ -298,13 +293,14 @@ public void checkDuplicateEmail(String email, Long currentUserId) { @Caching(evict = { @CacheEvict(value = AppCacheProperties.Types.USER_BY_NAME, allEntries = true), @CacheEvict(value = AppCacheProperties.Types.USER_BY_ID, key = "#id"), - @CacheEvict(value = AppCacheProperties.Types.USERS, allEntries = true) + @CacheEvict(value = AppCacheProperties.Types.USERS, allEntries = true), + @CacheEvict(value = AppCacheProperties.Types.TASKS, allEntries = true), + @CacheEvict(value = AppCacheProperties.Types.TASK_BY_ID, allEntries = true) }) - @Transactional + @LazySession(type = TransactionalType.AROUND) @LazyLogger public void deleteById(Long id) { - self.findById(id); - taskService.deleteAllByUserId(id); - userRepository.deleteById(id); + User user = self.findById(id); + userRepository.deleteById(user); } } diff --git a/src/main/java/com/emobile/springtodo/service/impl/security/SecurityServiceImpl.java b/src/main/java/com/emobile/springtodo/service/impl/security/SecurityServiceImpl.java index d2818182..ae5dc875 100644 --- a/src/main/java/com/emobile/springtodo/service/impl/security/SecurityServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/impl/security/SecurityServiceImpl.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.service.impl.security; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.service.UserService; import com.emobile.springtodo.service.security.SecurityService; diff --git a/src/main/java/com/emobile/springtodo/service/impl/security/UserDetailsServiceImpl.java b/src/main/java/com/emobile/springtodo/service/impl/security/UserDetailsServiceImpl.java index 071f0070..05272899 100644 --- a/src/main/java/com/emobile/springtodo/service/impl/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/emobile/springtodo/service/impl/security/UserDetailsServiceImpl.java @@ -1,6 +1,6 @@ package com.emobile.springtodo.service.impl.security; -import com.emobile.springtodo.aop.LazyLogger; +import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.security.AppUserDetails; import com.emobile.springtodo.service.UserService; diff --git a/src/main/java/com/emobile/springtodo/util/JdbcUtil.java b/src/main/java/com/emobile/springtodo/util/JdbcUtil.java deleted file mode 100644 index 4f289500..00000000 --- a/src/main/java/com/emobile/springtodo/util/JdbcUtil.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.emobile.springtodo.util; - -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.jdbc.core.RowMapper; - -import java.util.Optional; - -public class JdbcUtil { - private JdbcUtil() { - throw new IllegalStateException("Utility class"); - } - - /** - * Optional Extractor. - * - * @param mapper for map {@link T} entity - * @param entity type for mapping - * @return {@link Optional} mapped entity if ResultSet exist - */ - public static ResultSetExtractor> optionalExtractor( - RowMapper mapper) { - return rs -> rs.next() - ? Optional.of(mapper.mapRow(rs, 1)) - : Optional.empty(); - } -} diff --git a/src/main/java/com/emobile/springtodo/util/SessionUtil.java b/src/main/java/com/emobile/springtodo/util/SessionUtil.java new file mode 100644 index 00000000..493dd718 --- /dev/null +++ b/src/main/java/com/emobile/springtodo/util/SessionUtil.java @@ -0,0 +1,30 @@ +package com.emobile.springtodo.util; + +import lombok.RequiredArgsConstructor; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class SessionUtil { + private final SessionFactory sessionFactory; + private final ThreadLocal threadSession = new ThreadLocal<>(); + + public Session getSession() { + Session session = threadSession.get(); + if (session == null) { + session = sessionFactory.openSession(); + threadSession.set(session); + } + return session; + } + + public void closeSession() { + Session session = threadSession.get(); + threadSession.remove(); + if (session != null && session.isOpen()) { + session.close(); + } + } +} diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml index c2fee0c1..fccc94e2 100644 --- a/src/main/resources/config/application.yml +++ b/src/main/resources/config/application.yml @@ -6,6 +6,9 @@ spring: url: ${SERVER_URL:jdbc:postgresql://localhost:5432/spring_todo_db} username: ${SERVER_USERNAME:postgres} password: ${SERVER_PASS:1322} + jpa: + show-sql: true + open-in-view: true data: redis: port: ${REDIS_PORT:6379} diff --git a/src/test/java/com/emobile/springtodo/SpringToDoApplicationTests.java b/src/test/java/com/emobile/springtodo/SpringToDoApplicationTests.java index 17548caa..5fe400ae 100644 --- a/src/test/java/com/emobile/springtodo/SpringToDoApplicationTests.java +++ b/src/test/java/com/emobile/springtodo/SpringToDoApplicationTests.java @@ -1,5 +1,8 @@ package com.emobile.springtodo; +import com.emobile.springtodo.model.entity.Task; +import com.emobile.springtodo.model.entity.User; +import org.hibernate.SessionFactory; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -21,4 +24,32 @@ public Clock fixedClock() { return Clock.fixed(MILLENNIUM.toInstant(ZoneOffset.UTC), ZoneOffset.UTC); } } + +// @TestConfiguration +// static class CustomHibernateConfiguration { +// private final String dataSourceDriverClassName = "org.postgresql.Driver"; +// private final String dataSourceUrl = "jdbc:postgresql://localhost:5432/spring_todo_db"; +// private final String dataSourceUsername = "postgres"; +// private final String dataSourcePassword = "1322"; +//// @Bean +//// @Primary +//// public SessionUtil sessionUtil() { +//// return new SessionUtil(sessionFactory()); +//// } +// +// @Bean +// @Primary +// public SessionFactory sessionFactory() { +// return new org.hibernate.cfg.Configuration() +// .setProperty("hibernate.connection.driver_class", dataSourceDriverClassName) +// .setProperty("hibernate.connection.url", dataSourceUrl) +// .setProperty("hibernate.connection.username", dataSourceUsername) +// .setProperty("hibernate.connection.password", dataSourcePassword) +// .setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect") +// .setProperty("hibernate.show_sql", "true") +// .addAnnotatedClass(User.class) +// .addAnnotatedClass(Task.class) +// .buildSessionFactory(); +// } +// } } diff --git a/src/test/java/com/emobile/springtodo/controller/AuthControllerTest.java b/src/test/java/com/emobile/springtodo/controller/AuthControllerTest.java index cd0ea37a..ba47a4e1 100644 --- a/src/test/java/com/emobile/springtodo/controller/AuthControllerTest.java +++ b/src/test/java/com/emobile/springtodo/controller/AuthControllerTest.java @@ -1,7 +1,9 @@ package com.emobile.springtodo.controller; import com.redis.testcontainers.RedisContainer; +import org.flywaydb.core.Flyway; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -50,6 +52,15 @@ static void configureProperties(DynamicPropertyRegistry registry) { () -> REDIS_CONTAINER.getMappedPort(6379).toString()); } + @BeforeAll + static void beforeAll() { + POSTGRE_CONTAINER.start(); + Flyway flyway = Flyway.configure() + .dataSource(POSTGRE_CONTAINER.getJdbcUrl(), POSTGRE_CONTAINER.getUsername(), POSTGRE_CONTAINER.getPassword()) + .load(); + flyway.migrate(); + } + @AfterAll static void afterAll() { POSTGRE_CONTAINER.stop(); diff --git a/src/test/java/com/emobile/springtodo/controller/TaskControllerTest.java b/src/test/java/com/emobile/springtodo/controller/TaskControllerTest.java index 4ebbb0ad..5f14b382 100644 --- a/src/test/java/com/emobile/springtodo/controller/TaskControllerTest.java +++ b/src/test/java/com/emobile/springtodo/controller/TaskControllerTest.java @@ -1,19 +1,24 @@ package com.emobile.springtodo.controller; +import com.emobile.springtodo.model.entity.Task; +import com.emobile.springtodo.model.entity.TaskStatus; import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.security.AppUserDetails; import com.emobile.springtodo.model.security.RoleType; +import com.emobile.springtodo.util.SessionUtil; import com.redis.testcontainers.RedisContainer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.query.Query; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.DynamicPropertyRegistry; @@ -28,6 +33,7 @@ import java.time.Month; import java.time.format.DateTimeFormatter; import java.util.Collections; +import java.util.List; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -43,14 +49,16 @@ @DisplayName("TaskControllerTest tests") class TaskControllerTest { private static final String URL_TEMPLATE = "/api/task"; + private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); + private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); @Autowired - JdbcTemplate jdbcTemplate; + private SessionFactory sessionFactory; @Autowired private MockMvc mockMvc; @Autowired private RedisCacheManager cacheManager; - private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); - private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); + private Long existedId; + private Long existedUserId; @Container public static final PostgreSQLContainer POSTGRE_CONTAINER = @@ -73,6 +81,15 @@ static void configureProperties(DynamicPropertyRegistry registry) { () -> REDIS_CONTAINER.getMappedPort(6379).toString()); } + @BeforeAll + static void beforeAll() { + POSTGRE_CONTAINER.start(); + Flyway flyway = Flyway.configure() + .dataSource(POSTGRE_CONTAINER.getJdbcUrl(), POSTGRE_CONTAINER.getUsername(), POSTGRE_CONTAINER.getPassword()) + .load(); + flyway.migrate(); + } + @AfterAll static void afterAll() { POSTGRE_CONTAINER.stop(); @@ -81,8 +98,34 @@ static void afterAll() { @BeforeEach void setUp() { - jdbcTemplate.update("TRUNCATE users CASCADE"); cacheManager.getCacheNames().forEach(cacheName -> cacheManager.getCache(cacheName).clear()); + + Session session = sessionFactory.openSession(); + Transaction transaction = session.beginTransaction(); + + session.createQuery("DELETE FROM Task").executeUpdate(); + session.createQuery("DELETE FROM User").executeUpdate(); + + Task test1 = new Task(); + Task test2 = new Task(); + User testAuthor = new User(null, "user", "pass1", + "email1@co.m", RoleType.ROLE_USER, List.of(test1)); + User testAuthor2 = new User(null, "user2", "pass1", + "email2@co.m", RoleType.ROLE_USER, List.of(test2)); + test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor); + test2 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor2); + + session.persist(testAuthor); + session.persist(testAuthor2); + session.persist(test1); + session.persist(test2); + existedId = test1.getId(); + existedUserId = testAuthor.getId(); + + transaction.commit(); + session.close(); } @Test @@ -90,7 +133,7 @@ void setUp() { @DisplayName("getById test: get task data by id from anonymous user.") void givenAnonymousUserWhenGetByIdUrlThenStatusUnauthorized() throws Exception { - String getByIdUrl = URL_TEMPLATE + "/1"; + String getByIdUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(get(getByIdUrl)) .andExpect(status().isUnauthorized()); @@ -101,7 +144,7 @@ void givenAnonymousUserWhenGetByIdUrlThenStatusUnauthorized() @DisplayName("getById test: get task data by not existed user id.") void givenNotExistedUserIdWhenGetByIdUrlThenStatusNotFound() throws Exception { - String getByIdUrl = URL_TEMPLATE + "/1"; + String getByIdUrl = URL_TEMPLATE + "/" + (existedId + 2); mockMvc.perform(get(getByIdUrl)) .andExpect(status().isNotFound()); @@ -112,22 +155,14 @@ void givenNotExistedUserIdWhenGetByIdUrlThenStatusNotFound() @DisplayName("getById test: get task data by existed user id.") void givenExistedUserIdWhenGetByIdUrlThenUserResponse() throws Exception { - long expectedId = 1L; - String getByIdUrl = URL_TEMPLATE + "/" + expectedId; + String getByIdUrl = URL_TEMPLATE + "/" + existedId; String expectedName = "name"; - String expectedDescription = "description"; - String expectedStatus = "DONE"; - long expectedAuthorId = 2L; - String sql = """ - INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?,?) - """; - - setDefaultAuthorUser(expectedAuthorId); - jdbcTemplate.update(sql, - expectedId, expectedName, expectedDescription, expectedStatus, - BEFORE_MILLENNIUM, MILLENNIUM, expectedAuthorId - ); + String expectedDescription = "des"; + String expectedStatus = TaskStatus.TODO.name(); + Long expectedAuthorId = sessionFactory.openSession() + .find(Task.class, existedId) + .getAuthor() + .getId(); mockMvc.perform(get(getByIdUrl)) .andExpect(jsonPath("$.id").isNumber()) @@ -137,7 +172,7 @@ INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_ .andExpect(jsonPath("$.createdAt").isString()) .andExpect(jsonPath("$.updatedAt").isString()) .andExpect(jsonPath("$.authorId").isNumber()) - .andExpect(jsonPath("$.id").value(expectedId)) + .andExpect(jsonPath("$.id").value(existedId)) .andExpect(jsonPath("$.name").value(expectedName)) .andExpect(jsonPath("$.description").value(expectedDescription)) .andExpect(jsonPath("$.status").value(expectedStatus)) @@ -162,27 +197,18 @@ void givenAnonymousUserWhenSaveUrlThenStatusUnauthorized() @DisplayName("save test: save task data by user id from auth user.") void givenSaveJsonWhenSaveUrlThenStatusCreated() throws Exception { - long expectedId = 1L; - String expectedName = "name"; + String expectedName = "nameUpd"; String expectedDescription = "description"; String expectedStatus = "DONE"; - long expectedAuthorId = 2L; String requestUserJson = """ { - "name": "name", + "name": "nameUpd", "description": "description", "status": "DONE" }"""; - User defaultUser = new User( - expectedAuthorId, - "username", - "pass", - "email@c.om", - RoleType.ROLE_USER, - Collections.emptyList() - ); - AppUserDetails principal = new AppUserDetails(defaultUser); - setDefaultAuthorUser(expectedAuthorId); + User user = sessionFactory.openSession() + .find(User.class, existedUserId); + AppUserDetails principal = new AppUserDetails(user); mockMvc.perform(post(URL_TEMPLATE) .with(user(principal)) @@ -196,7 +222,6 @@ void givenSaveJsonWhenSaveUrlThenStatusCreated() .andExpect(jsonPath("$.createdAt").isString()) .andExpect(jsonPath("$.updatedAt").isString()) .andExpect(jsonPath("$.authorId").isNumber()) - .andExpect(jsonPath("$.id").value(expectedId)) .andExpect(jsonPath("$.name").value(expectedName)) .andExpect(jsonPath("$.description").value(expectedDescription)) .andExpect(jsonPath("$.status").value(expectedStatus)) @@ -204,7 +229,7 @@ void givenSaveJsonWhenSaveUrlThenStatusCreated() .value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(MILLENNIUM))) .andExpect(jsonPath("$.updatedAt") .value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(MILLENNIUM))) - .andExpect(jsonPath("$.authorId").value(expectedAuthorId)) + .andExpect(jsonPath("$.authorId").value(existedUserId)) .andExpect(status().isCreated()); } @@ -213,7 +238,7 @@ void givenSaveJsonWhenSaveUrlThenStatusCreated() @DisplayName("update test: update task data by id from anonymous user.") void givenAnonymousUserWhenUpdateUrlThenStatusUnauthorized() throws Exception { - String updateUrl = URL_TEMPLATE + "/1"; + String updateUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(put(updateUrl)) .andExpect(status().isUnauthorized()); @@ -224,28 +249,16 @@ void givenAnonymousUserWhenUpdateUrlThenStatusUnauthorized() @DisplayName("update test: update task data by user id from auth user.") void givenInsertJsonWhenUpdateUrlThenTaskResponse() throws Exception { - long expectedId = 1L; - String updateUrl = URL_TEMPLATE + "/" + expectedId; - String expectedName = "name"; + String updateUrl = URL_TEMPLATE + "/" + existedId; + String expectedName = "nameUpd"; String expectedDescription = "description"; - String expectedStatus = "DONE"; - long expectedAuthorId = 2L; + String expectedStatus = TaskStatus.TODO.name(); String requestUserJson = """ { - "name": "name", + "name": "nameUpd", "description": "description", - "status": "DONE" + "status": "TODO" }"""; - String sql = """ - INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?,?) - """; - setDefaultAuthorUser(expectedAuthorId); - - jdbcTemplate.update(sql, - expectedId, expectedName, expectedDescription, expectedStatus, - BEFORE_MILLENNIUM, MILLENNIUM, expectedAuthorId - ); mockMvc.perform(put(updateUrl) .contentType(MediaType.APPLICATION_JSON) @@ -258,7 +271,7 @@ INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_ .andExpect(jsonPath("$.createdAt").isString()) .andExpect(jsonPath("$.updatedAt").isString()) .andExpect(jsonPath("$.authorId").isNumber()) - .andExpect(jsonPath("$.id").value(expectedId)) + .andExpect(jsonPath("$.id").value(existedId)) .andExpect(jsonPath("$.name").value(expectedName)) .andExpect(jsonPath("$.description").value(expectedDescription)) .andExpect(jsonPath("$.status").value(expectedStatus)) @@ -266,7 +279,7 @@ INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_ .value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(BEFORE_MILLENNIUM))) .andExpect(jsonPath("$.updatedAt") .value(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(MILLENNIUM))) - .andExpect(jsonPath("$.authorId").value(expectedAuthorId)) + .andExpect(jsonPath("$.authorId").value(existedUserId)) .andExpect(status().isOk()); } @@ -275,7 +288,7 @@ INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_ @DisplayName("delete test: delete user data by id from anonymous user.") void givenAnonymousUserWhenDeleteUrlThenStatusUnauthorized() throws Exception { - String deleteUrl = URL_TEMPLATE + "/1"; + String deleteUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(delete(deleteUrl)) .andExpect(status().isUnauthorized()); @@ -286,7 +299,7 @@ void givenAnonymousUserWhenDeleteUrlThenStatusUnauthorized() @DisplayName("delete test: delete user data by not existed id.") void givenNotExistedUserIdWhenDeleteUrlThenStatusNotFound() throws Exception { - String deleteUrl = URL_TEMPLATE + "/1"; + String deleteUrl = URL_TEMPLATE + "/" + (existedId + 2); mockMvc.perform(delete(deleteUrl)) .andExpect(status().isNotFound()); @@ -297,38 +310,9 @@ void givenNotExistedUserIdWhenDeleteUrlThenStatusNotFound() @DisplayName("delete test: delete user data by existed id from auth user.") void givenExistedUserIdWhenDeleteUrlThenStatusNoContent() throws Exception { - long expectedId = 1L; - String deleteUrl = URL_TEMPLATE + "/" + expectedId; - String expectedName = "name"; - String expectedDescription = "description"; - String expectedStatus = "DONE"; - long expectedAuthorId = 2L; - String sql = """ - INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?,?) - """; - - setDefaultAuthorUser(expectedAuthorId); - jdbcTemplate.update(sql, - expectedId, expectedName, expectedDescription, expectedStatus, - BEFORE_MILLENNIUM, MILLENNIUM, expectedAuthorId - ); + String deleteUrl = URL_TEMPLATE + "/" + existedId; + mockMvc.perform(delete(deleteUrl)) .andExpect(status().isNoContent()); } - - private void setDefaultAuthorUser(long expectedId) { - String expectedUsername = "username"; - String expectedPass = "pass"; - String expectedEmail = "email@c.om"; - String expectedRole = "ROLE_USER"; - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """; - jdbcTemplate.update(sql, - expectedId, expectedUsername, expectedPass, - expectedEmail, expectedRole - ); - } } diff --git a/src/test/java/com/emobile/springtodo/controller/UserControllerTest.java b/src/test/java/com/emobile/springtodo/controller/UserControllerTest.java index db3bff9b..b5c1f599 100644 --- a/src/test/java/com/emobile/springtodo/controller/UserControllerTest.java +++ b/src/test/java/com/emobile/springtodo/controller/UserControllerTest.java @@ -1,16 +1,23 @@ package com.emobile.springtodo.controller; +import com.emobile.springtodo.model.entity.Task; +import com.emobile.springtodo.model.entity.TaskStatus; +import com.emobile.springtodo.model.entity.User; +import com.emobile.springtodo.model.security.RoleType; +import com.emobile.springtodo.repository.impl.UserRepositoryImpl; +import com.emobile.springtodo.util.SessionUtil; import com.redis.testcontainers.RedisContainer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.flywaydb.core.Flyway; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.query.Query; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.http.MediaType; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; @@ -23,6 +30,10 @@ import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; +import java.time.LocalDateTime; +import java.time.Month; +import java.util.List; + import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -34,14 +45,18 @@ @DisplayName("UserControllerTest tests") class UserControllerTest { private static final String URL_TEMPLATE = "/api/user"; + private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); + private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); @Autowired - JdbcTemplate jdbcTemplate; + private SessionFactory sessionFactory; @MockitoBean PasswordEncoder passwordEncoder; @Autowired private MockMvc mockMvc; @Autowired private RedisCacheManager cacheManager; + private Long existedId; + @Container public static final PostgreSQLContainer POSTGRE_CONTAINER = @@ -64,6 +79,15 @@ static void configureProperties(DynamicPropertyRegistry registry) { () -> REDIS_CONTAINER.getMappedPort(6379).toString()); } + @BeforeAll + static void beforeAll() { + POSTGRE_CONTAINER.start(); + Flyway flyway = Flyway.configure() + .dataSource(POSTGRE_CONTAINER.getJdbcUrl(), POSTGRE_CONTAINER.getUsername(), POSTGRE_CONTAINER.getPassword()) + .load(); + flyway.migrate(); + } + @AfterAll static void afterAll() { POSTGRE_CONTAINER.stop(); @@ -72,8 +96,33 @@ static void afterAll() { @BeforeEach void setUp() { - jdbcTemplate.update("TRUNCATE users CASCADE"); cacheManager.getCacheNames().forEach(cacheName -> cacheManager.getCache(cacheName).clear()); + + Session session = sessionFactory.openSession(); + Transaction transaction = session.beginTransaction(); + + session.createQuery("DELETE FROM Task").executeUpdate(); + session.createQuery("DELETE FROM User").executeUpdate(); + + Task test1 = new Task(); + Task test2 = new Task(); + User testAuthor = new User(null, "user", "pass1", + "email1@co.m", RoleType.ROLE_USER, List.of(test1)); + User testAuthor2 = new User(null, "user2", "pass1", + "email2@co.m", RoleType.ROLE_USER, List.of(test2)); + test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor); + test2 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor2); + + session.persist(testAuthor); + session.persist(testAuthor2); + session.persist(test1); + session.persist(test2); + existedId = testAuthor.getId(); + + transaction.commit(); + session.close(); } @Test @@ -81,7 +130,7 @@ void setUp() { @DisplayName("getById test: get user data by id from anonymous user.") void givenAnonymousUserWhenGetByIdUrlThenStatusUnauthorized() throws Exception { - String getByIdUrl = URL_TEMPLATE + "/1"; + String getByIdUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(get(getByIdUrl)) .andExpect(status().isUnauthorized()); @@ -92,7 +141,7 @@ void givenAnonymousUserWhenGetByIdUrlThenStatusUnauthorized() @DisplayName("getById test: get user data by not existed user id.") void givenNotExistedUserIdWhenGetByIdUrlThenStatusNotFound() throws Exception { - String getByIdUrl = URL_TEMPLATE + "/1"; + String getByIdUrl = URL_TEMPLATE + "/" + (existedId + 2); mockMvc.perform(get(getByIdUrl)) .andExpect(status().isNotFound()); @@ -103,20 +152,11 @@ void givenNotExistedUserIdWhenGetByIdUrlThenStatusNotFound() @DisplayName("getById test: get user data by existed user id.") void givenExistedUserIdWhenGetByIdUrlThenUserResponse() throws Exception { - long expectedId = 1L; - String getByIdUrl = URL_TEMPLATE + "/" + expectedId; - String expectedUsername = "username"; - String expectedPass = "encodedPass"; - String expectedEmail = "email@c.om"; - String expectedRole = "ROLE_ADMIN"; - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """; - jdbcTemplate.update(sql, - expectedId, expectedUsername, expectedPass, - expectedEmail, expectedRole - ); + String getByIdUrl = URL_TEMPLATE + "/" + existedId; + String expectedUsername = "user"; + String expectedPass = "pass1"; + String expectedEmail = "email1@co.m"; + String expectedRole = "ROLE_USER"; mockMvc.perform(get(getByIdUrl)) .andExpect(jsonPath("$.id").isNumber()) @@ -124,7 +164,7 @@ INSERT INTO users(id, username, password, email, role) .andExpect(jsonPath("$.password").isString()) .andExpect(jsonPath("$.email").isString()) .andExpect(jsonPath("$.role").isString()) - .andExpect(jsonPath("$.id").value(expectedId)) + .andExpect(jsonPath("$.id").value(existedId)) .andExpect(jsonPath("$.username").value(expectedUsername)) .andExpect(jsonPath("$.password").value(expectedPass)) .andExpect(jsonPath("$.email").value(expectedEmail)) @@ -137,7 +177,7 @@ INSERT INTO users(id, username, password, email, role) @DisplayName("update test: update user data by id from anonymous user.") void givenAnonymousUserWhenUpdateUrlThenStatusUnauthorized() throws Exception { - String updateUrl = URL_TEMPLATE + "/1"; + String updateUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(put(updateUrl)) .andExpect(status().isUnauthorized()); @@ -148,29 +188,18 @@ void givenAnonymousUserWhenUpdateUrlThenStatusUnauthorized() @DisplayName("update test: update user data by user id from auth user.") void givenInsertJsonWhenUpdateUrlThenUserResponse() throws Exception { - long expectedId = 1L; - String updateUrl = URL_TEMPLATE + "/" + expectedId; - String oldUsername = "username"; - String oldPass = "password"; - String oldEmail = "email@c.om"; - String oldRole = "ROLE_ADMIN"; + String updateUrl = URL_TEMPLATE + "/" + existedId; + String oldEmail = "email@co.m"; + String oldRole = "ROLE_USER"; String expectedUsername = "user"; String expectedPass = "encodedPass"; String requestUserJson = """ { "username": "user", "password": "pass", - "email" : "email@c.om", - "roles": "ROLE_ADMIN" + "email" : "email@co.m", + "roles": "ROLE_USER" }"""; - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """;// - jdbcTemplate.update(sql, - expectedId, oldUsername, oldPass, - oldEmail, oldRole - ); when(passwordEncoder.encode("pass")) .thenReturn(expectedPass); @@ -184,7 +213,7 @@ INSERT INTO users(id, username, password, email, role) .andExpect(jsonPath("$.password").isString()) .andExpect(jsonPath("$.email").isString()) .andExpect(jsonPath("$.role").isString()) - .andExpect(jsonPath("$.id").value(expectedId)) + .andExpect(jsonPath("$.id").value(existedId)) .andExpect(jsonPath("$.username").value(expectedUsername)) .andExpect(jsonPath("$.password").value(expectedPass)) .andExpect(jsonPath("$.email").value(oldEmail)) @@ -197,7 +226,7 @@ INSERT INTO users(id, username, password, email, role) @DisplayName("delete test: delete user data by id from anonymous user.") void givenAnonymousUserWhenDeleteUrlThenStatusUnauthorized() throws Exception { - String deleteUrl = URL_TEMPLATE + "/1"; + String deleteUrl = URL_TEMPLATE + "/" + existedId; mockMvc.perform(delete(deleteUrl)) .andExpect(status().isUnauthorized()); @@ -208,7 +237,7 @@ void givenAnonymousUserWhenDeleteUrlThenStatusUnauthorized() @DisplayName("delete test: delete user data by not existed id.") void givenNotExistedUserIdWhenDeleteUrlThenStatusNotFound() throws Exception { - String deleteUrl = URL_TEMPLATE + "/1"; + String deleteUrl = URL_TEMPLATE + "/" + (existedId + 2); mockMvc.perform(delete(deleteUrl)) .andExpect(status().isNotFound()); @@ -219,20 +248,8 @@ void givenNotExistedUserIdWhenDeleteUrlThenStatusNotFound() @DisplayName("delete test: delete user data by existed id from auth user.") void givenExistedUserIdWhenDeleteUrlThenStatusNoContent() throws Exception { - long expectedId = 1L; - String deleteUrl = URL_TEMPLATE + "/" + expectedId; - String expectedUsername = "username"; - String expectedPass = "encodedPass"; - String expectedEmail = "email@c.om"; - String expectedRole = "ROLE_ADMIN"; - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """; - jdbcTemplate.update(sql, - expectedId, expectedUsername, expectedPass, - expectedEmail, expectedRole - ); + String deleteUrl = URL_TEMPLATE + "/" + existedId; + mockMvc.perform(delete(deleteUrl)) .andExpect(status().isNoContent()); } diff --git a/src/test/java/com/emobile/springtodo/repository/impl/TaskRepositoryImplTest.java b/src/test/java/com/emobile/springtodo/repository/impl/TaskRepositoryImplTest.java index cd45bfda..fc4df98a 100644 --- a/src/test/java/com/emobile/springtodo/repository/impl/TaskRepositoryImplTest.java +++ b/src/test/java/com/emobile/springtodo/repository/impl/TaskRepositoryImplTest.java @@ -6,15 +6,17 @@ import com.emobile.springtodo.model.security.RoleType; import com.emobile.springtodo.model.util.Page; import com.emobile.springtodo.model.util.PageInfo; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import com.emobile.springtodo.util.SessionUtil; +import org.flywaydb.core.Flyway; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.query.Query; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; @@ -26,18 +28,17 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +//@DataJpaTest +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers @DisplayName("TaskRepositoryImplTest Tests") class TaskRepositoryImplTest { private TaskRepositoryImpl repository; - @Autowired - private JdbcTemplate jdbcTemplate; + // @Autowired + private SessionUtil sessionUtil; + private static SessionFactory sessionFactory; private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); @@ -46,11 +47,25 @@ class TaskRepositoryImplTest { new PostgreSQLContainer<>("postgres:latest") .withReuse(true); - @DynamicPropertySource - static void configureProperties(DynamicPropertyRegistry registry) { - registry.add("spring.datasource.url", postgreContainer::getJdbcUrl); - registry.add("spring.datasource.username", postgreContainer::getUsername); - registry.add("spring.datasource.password", postgreContainer::getPassword); + @BeforeAll + static void beforeAll() { + postgreContainer.start(); + Flyway flyway = Flyway.configure() + .dataSource(postgreContainer.getJdbcUrl(), postgreContainer.getUsername(), postgreContainer.getPassword()) + .load(); + flyway.migrate(); + + Configuration configuration = new Configuration() + .setProperty("hibernate.connection.url", postgreContainer.getJdbcUrl()) + .setProperty("hibernate.connection.username", postgreContainer.getUsername()) + .setProperty("hibernate.connection.password", postgreContainer.getPassword()) + .setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect") + .setProperty("hibernate.hbm2ddl.auto", "create-drop") + .setProperty("hibernate.show_sql", "true") + .addAnnotatedClass(User.class) + .addAnnotatedClass(Task.class); + + sessionFactory = configuration.buildSessionFactory(); } @AfterAll @@ -60,28 +75,42 @@ static void afterAll() { @BeforeEach void setUp() { - repository = new TaskRepositoryImpl(jdbcTemplate); - jdbcTemplate.update("TRUNCATE tasks CASCADE"); - jdbcTemplate.update("TRUNCATE users CASCADE"); + sessionUtil = new SessionUtil(sessionFactory); + repository = new TaskRepositoryImpl(sessionUtil); + + Session session = sessionFactory.openSession(); + Transaction transaction = session.beginTransaction(); + + session.createQuery("DELETE FROM Task").executeUpdate(); + session.createQuery("DELETE FROM User").executeUpdate(); + + transaction.commit(); + session.close(); } @Test @DisplayName("findAll test: get all user data.") void givenPageInfoWhenGetAllThenListUser() { - PageInfo pageInfo = new PageInfo(5, 1); - Task test1 = new Task(2L, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, 3L); - Task test2 = new Task(4L, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, 2L); + Session session = sessionUtil.getSession(); + PageInfo pageInfo = new PageInfo(5, 0); + Task test1 = new Task(); + Task test2 = new Task(); + User testAuthor = new User(null, "user", "pass1", + "email1@co.m", RoleType.ROLE_USER, List.of(test1)); + User testAuthor2 = new User(null, "user2", "pass1", + "email2@co.m", RoleType.ROLE_USER, List.of(test2)); + test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor); + test2 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor2); Page expected = new Page<>(List.of(test1, test2)); - User testAuthor = new User(3L, "user", "pass1", - "email1@co.m", RoleType.ROLE_USER, List.of()); - User testAuthor2 = new User(2L, "user2", "pass1", - "email2@co.m", RoleType.ROLE_USER, List.of()); - addToDb(testAuthor); - addToDb(testAuthor2); - addToDb(test1); - addToDb(test2); + + session.beginTransaction(); + session.persist(testAuthor); + session.persist(testAuthor2); + session.persist(test1); + session.persist(test2); + session.getTransaction().commit(); Page actual = repository.findAll(pageInfo); @@ -91,13 +120,18 @@ void givenPageInfoWhenGetAllThenListUser() { @Test @DisplayName("findById test: get user data by id.") void givenExistingIdWhenGetByIdThenUser() { - Long taskId = 2L; - Task test1 = new Task(taskId, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, 4L); - User testAuthor = new User(4L, "user", "pass1", - "email1@co.m", RoleType.ROLE_USER, List.of()); - addToDb(testAuthor); - addToDb(test1); + Session session = sessionUtil.getSession(); + Task test1 = new Task(); + User testAuthor = new User(null, "user", "pass1", + "email1@co.m", RoleType.ROLE_USER, List.of(test1)); + test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor); + + session.beginTransaction(); + session.persist(testAuthor); + session.persist(test1); + Long taskId = test1.getId(); + session.getTransaction().commit(); Optional actual = repository.findById(taskId); @@ -118,80 +152,43 @@ void givenNotExistingIdWhenGetByIdThenThrow() { @Test @DisplayName("update test: send task data to repository.") void givenTaskWhenUpdateThenUpdatedTask() { - Task test1 = new Task(2L, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, 3L); - Task expected = new Task(2L, "name2", "des2", - TaskStatus.DONE, BEFORE_MILLENNIUM, MILLENNIUM, 3L); - User testAuthor = new User(3L, "user", "pass1", + Session session = sessionUtil.getSession(); + User testAuthor = new User(null, "user", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of()); - addToDb(testAuthor); - addToDb(test1); + Task test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, testAuthor); + + session.beginTransaction(); + session.persist(testAuthor); + session.persist(test1); + Long taskId = test1.getId(); + session.getTransaction().commit(); + + Task expected = new Task(taskId, "name2", "des2", + TaskStatus.DONE, BEFORE_MILLENNIUM, MILLENNIUM, testAuthor); - repository.update(expected); + Task actual = repository.update(expected); - assertTrue(existsInDb(expected)); + session.find(Task.class, taskId); + assertEquals(expected, actual); } @Test @DisplayName("delete test: delete task data message to repository.") void givenTaskIdWhenDeleteThenVoid() { - Long taskId = 2L; - Task test1 = new Task(taskId, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, 3L); - User testAuthor = new User(3L, "user", "pass1", + Session session = sessionUtil.getSession(); + User testAuthor = new User(null, "user", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of()); - addToDb(testAuthor); - addToDb(test1); - - repository.deleteById(taskId); - assertFalse(existsInDb(test1)); - } - - private void addToDb(User user) { - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """; - jdbcTemplate.update(sql, - user.getId(), user.getUsername(), user.getPassword(), - user.getEmail(), user.getRole().name() - ); - } - - private void addToDb(Task task) { - String sql = """ - INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?,?) - """; - jdbcTemplate.update(sql, - task.getId(), task.getName(), task.getDescription(), - task.getStatus().name(), task.getCreatedAt(), - task.getUpdatedAt(), task.getAuthorId() - ); - } + Task test1 = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, testAuthor); + session.beginTransaction(); + session.persist(testAuthor); + session.persist(test1); + session.getTransaction().commit(); - private boolean existsInDb(Task task) { - String sql = """ - SELECT EXISTS (SELECT * - FROM tasks - WHERE name = ? - AND description = ? - AND status = ? - AND created_at = ? - AND updated_at = ? - AND author_id = ?) - AS result - """; - return jdbcTemplate.queryForObject(sql, getExistsMapper(), - task.getName(), task.getDescription(), - task.getStatus().name(), task.getCreatedAt(), - task.getUpdatedAt(), task.getAuthorId() - ); - } + repository.deleteById(test1); - private RowMapper getExistsMapper() { - return (resultSet, rowNum) -> - resultSet.getString("result") - .equals("t"); + Task actual = session.find(Task.class, test1.getId()); + assertNull(actual); } } diff --git a/src/test/java/com/emobile/springtodo/repository/impl/UserRepositoryImplTest.java b/src/test/java/com/emobile/springtodo/repository/impl/UserRepositoryImplTest.java index d3a928cd..98ab8f3e 100644 --- a/src/test/java/com/emobile/springtodo/repository/impl/UserRepositoryImplTest.java +++ b/src/test/java/com/emobile/springtodo/repository/impl/UserRepositoryImplTest.java @@ -6,15 +6,25 @@ import com.emobile.springtodo.model.security.RoleType; import com.emobile.springtodo.model.util.Page; import com.emobile.springtodo.model.util.PageInfo; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import com.emobile.springtodo.util.SessionUtil; +import jakarta.persistence.EntityManager; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.internal.jdbc.JdbcTemplate; +import org.flywaydb.core.internal.jdbc.RowMapper; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.annotations.NamedQuery; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.query.Query; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.PostgreSQLContainer; @@ -26,18 +36,20 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -@DataJpaTest -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +//@DataJpaTest +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +//@SpringBootTest @Testcontainers @DisplayName("UserRepositoryImpl Tests") class UserRepositoryImplTest { + // @Autowired private UserRepositoryImpl repository; - @Autowired - private JdbcTemplate jdbcTemplate; + // @Autowired + private SessionUtil sessionUtil; + private static SessionFactory sessionFactory; + private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); @Container @@ -52,6 +64,27 @@ static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.password", postgreContainer::getPassword); } + @BeforeAll + static void beforeAll() { + postgreContainer.start(); + Flyway flyway = Flyway.configure() + .dataSource(postgreContainer.getJdbcUrl(), postgreContainer.getUsername(), postgreContainer.getPassword()) + .load(); + flyway.migrate(); + + Configuration configuration = new Configuration() + .setProperty("hibernate.connection.url", postgreContainer.getJdbcUrl()) + .setProperty("hibernate.connection.username", postgreContainer.getUsername()) + .setProperty("hibernate.connection.password", postgreContainer.getPassword()) + .setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect") + .setProperty("hibernate.hbm2ddl.auto", "create-drop") + .setProperty("hibernate.show_sql", "true") + .addAnnotatedClass(User.class) + .addAnnotatedClass(Task.class); + + sessionFactory = configuration.buildSessionFactory(); + } + @AfterAll static void afterAll() { postgreContainer.stop(); @@ -59,21 +92,39 @@ static void afterAll() { @BeforeEach void setUp() { - repository = new UserRepositoryImpl(jdbcTemplate); - jdbcTemplate.update("TRUNCATE users CASCADE"); + sessionUtil = new SessionUtil(sessionFactory); + repository = new UserRepositoryImpl(sessionUtil); + + Session session = sessionFactory.openSession(); + Transaction transaction = session.beginTransaction(); + + session.createQuery("DELETE FROM Task").executeUpdate(); + session.createQuery("DELETE FROM User").executeUpdate(); + + transaction.commit(); + session.close(); + } + + @AfterEach + void after() { + sessionUtil.closeSession(); } @Test @DisplayName("findAll test: get all user data.") void givenPageInfoWhenGetAllThenListUser() { - PageInfo pageInfo = new PageInfo(5, 1); - User test1 = new User(2L, "test1", "pass1", + PageInfo pageInfo = new PageInfo(5, 0); + User test1 = new User(null, "test1", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of()); - User test2 = new User(3L, "test2", "pass2", + User test2 = new User(null, "test2", "pass2", "email2@co.m", RoleType.ROLE_ADMIN, List.of()); Page expected = new Page<>(List.of(test1, test2)); - addToDb(test1); - addToDb(test2); + + Session session = sessionUtil.getSession(); + Transaction transaction = session.beginTransaction(); + sessionUtil.getSession().persist(test1); + sessionUtil.getSession().persist(test2); + transaction.commit(); Page actual = repository.findAll(pageInfo); @@ -83,18 +134,24 @@ void givenPageInfoWhenGetAllThenListUser() { @Test @DisplayName("findById test: get user data by id.") void givenExistingIdWhenGetByIdThenUser() { - Long userId = 2L; - Task testTask = new Task(2L, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, userId); - User test1 = new User(userId, "test1", "pass1", + Session session = sessionUtil.getSession(); + Transaction transaction = session.beginTransaction(); + Task testTask = new Task(); + User test1 = new User(null, "test1", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of(testTask)); - addToDb(test1); - addToDb(testTask); + testTask = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, test1); + + sessionUtil.getSession().persist(test1); + sessionUtil.getSession().persist(testTask); + Long userId = test1.getId(); + transaction.commit(); Optional actual = repository.findById(userId); assertTrue(actual.isPresent()); assertEquals(test1, actual.get()); + assertEquals(1, actual.get().getTaskList().size()); } @Test @@ -103,6 +160,7 @@ void givenNotExistingIdWhenGetByIdThenThrow() { Long userId = 1L; Optional actual = repository.findById(userId); + actual.ifPresent(System.out::println); assertTrue(actual.isEmpty()); } @@ -111,12 +169,17 @@ void givenNotExistingIdWhenGetByIdThenThrow() { @DisplayName("findByUsername test: get user data by name.") void givenExistingNameWhenGetByIdThenUser() { String userUsername = "user"; - Task testTask = new Task(2L, "name", "des", - TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, 2L); - User test1 = new User(2L, userUsername, "pass1", + Task testTask = new Task(); + User test1 = new User(null, userUsername, "pass1", "email1@co.m", RoleType.ROLE_USER, List.of(testTask)); - addToDb(test1); - addToDb(testTask); + testTask = new Task(null, "name", "des", + TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, test1); + + Session session = sessionUtil.getSession(); + Transaction transaction = session.beginTransaction(); + sessionUtil.getSession().persist(test1); + sessionUtil.getSession().persist(testTask); + transaction.commit(); Optional actual = repository.findByUsername(userUsername); @@ -137,82 +200,64 @@ void givenNotExistingNameWhenGetByIdThenUser() { @Test @DisplayName("save test: send user data to repository.") void givenUserWhenSendUserToDbThenSavedUser() { + Session session = sessionUtil.getSession(); User userToSave = new User(null, "user", "pass", "email2@co.m", RoleType.ROLE_USER, List.of()); - repository.save(userToSave); + session.beginTransaction(); + User actual = repository.save(userToSave); + session.getTransaction().commit(); - assertTrue(existsInDb(userToSave)); + User inDb = session.find(User.class, actual.getId()); + + assertNotNull(actual); + assertNotNull(inDb); + assertEquals(inDb, actual); + assertSame(inDb, actual); } @Test @DisplayName("update test: send user data to repository.") void givenUserWhenUpdateThenUpdatedUser() { - User test1 = new User(2L, "user", "pass1", + Session session = sessionUtil.getSession(); + User test1 = new User(null, "user", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of()); - User expected = new User(2L, "user", "pass", - "email2@co.m", RoleType.ROLE_USER, List.of()); + + session.beginTransaction(); addToDb(test1); + Long userId = test1.getId(); + session.getTransaction().commit(); - repository.update(expected); + User expected = new User(userId, "user", "pass", + "email2@co.m", RoleType.ROLE_USER, List.of()); - assertTrue(existsInDb(expected)); + User actual = repository.update(expected); + + session.find(User.class, userId); + assertEquals(expected, actual); } @Test @DisplayName("delete test: delete user data message to repository.") void givenUserIdWhenDeleteThenVoid() { - Long userId = 1L; - User test1 = new User(userId, "user", "pass1", + Session session = sessionUtil.getSession(); + User test1 = new User(null, "user", "pass1", "email1@co.m", RoleType.ROLE_USER, List.of()); - addToDb(test1); + session.beginTransaction(); + session.persist(test1); + session.getTransaction().commit(); + + repository.deleteById(test1); - repository.deleteById(userId); - assertFalse(existsInDb(test1)); + User actual = session.find(User.class, test1.getId()); + assertNull(actual); } private void addToDb(User user) { - String sql = """ - INSERT INTO users(id, username, password, email, role) - VALUES (?,?,?,?,?) - """; - jdbcTemplate.update(sql, - user.getId(), user.getUsername(), user.getPassword(), - user.getEmail(), user.getRole().name() - ); + sessionUtil.getSession().persist(user); } private void addToDb(Task task) { - String sql = """ - INSERT INTO tasks(id, name, description, status, created_at, updated_at, author_id) - VALUES (?,?,?,?,?,?,?) - """; - jdbcTemplate.update(sql, - task.getId(), task.getName(), task.getDescription(), - task.getStatus().name(), task.getCreatedAt(), - task.getUpdatedAt(), task.getAuthorId() - ); - } - - private boolean existsInDb(User user) { - String sql = """ - SELECT EXISTS (SELECT * - FROM users - WHERE username = ? - AND password = ? - AND email = ? - AND role = ?) - AS result - """; - return jdbcTemplate.queryForObject(sql, getExistsMapper(), - user.getUsername(), user.getPassword(), - user.getEmail(), user.getRole().name() - ); - } - - private RowMapper getExistsMapper() { - return (resultSet, rowNum) -> - resultSet.getString("result") - .equals("t"); + sessionUtil.getSession().persist(task); } } diff --git a/src/test/java/com/emobile/springtodo/service/impl/TaskServiceImplTest.java b/src/test/java/com/emobile/springtodo/service/impl/TaskServiceImplTest.java index 13eea52f..ba1cefa9 100644 --- a/src/test/java/com/emobile/springtodo/service/impl/TaskServiceImplTest.java +++ b/src/test/java/com/emobile/springtodo/service/impl/TaskServiceImplTest.java @@ -38,11 +38,13 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) -@DisplayName("UserServiceImplTest Tests") +@DisplayName("TaskServiceImplTest Tests") class TaskServiceImplTest { private TaskServiceImpl taskService; @Mock private TaskRepository taskRepository; + @Mock + private UserServiceImpl userService; private static final LocalDateTime MILLENNIUM = LocalDateTime.of(2000, Month.JANUARY, 1, 0, 0, 0); private static final LocalDateTime BEFORE_MILLENNIUM = MILLENNIUM.minusDays(5); private final Clock clock = Clock.fixed(MILLENNIUM.toInstant(ZoneOffset.UTC), ZoneOffset.UTC); @@ -50,33 +52,41 @@ class TaskServiceImplTest { @BeforeEach void setUp() { - taskService = new TaskServiceImpl(taskRepository, clock); + taskService = new TaskServiceImpl(taskRepository, userService, clock); taskService.setSelf(taskService); } @Test @DisplayName("findAll test: get all user data.") - void givenWhenGetAllThenListUser() { - List userList = new ArrayList<>(List.of( + void givenWhenGetAllThenListTask() { + List taskList = new ArrayList<>(List.of( new Task(), new Task() )); PageInfo pageInfo = new PageInfo(0, 10); when(taskRepository.findAll(pageInfo)) - .thenReturn(new Page<>(userList)); + .thenReturn(new Page<>(taskList)); List actual = taskService.findAll(pageInfo); - assertEquals(userList.size(), actual.size()); + assertEquals(taskList.size(), actual.size()); verify(taskRepository, times(1)) .findAll(pageInfo); } @Test - @DisplayName("findById test: get user data by id.") - void givenExistingIdWhenGetByIdThenUser() { - Long userId = 1L; + @DisplayName("findById test: get task data by id.") + void givenExistingIdWhenGetByIdThenTask() { + Long taskId = 1L; + User defaultUser = new User( + 2L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); Task defaultTask = new Task( 1L, "name", @@ -84,13 +94,13 @@ void givenExistingIdWhenGetByIdThenUser() { TaskStatus.DONE, LocalDateTime.of(2, 2, 2, 2, 2), LocalDateTime.of(2, 2, 2, 2, 2), - 2L + defaultUser ); - when(taskRepository.findById(userId)) + when(taskRepository.findById(taskId)) .thenReturn(Optional.of(defaultTask)); - Task actual = taskService.findById(userId); + Task actual = taskService.findById(taskId); assertEquals(defaultTask, actual); verify(taskRepository, times(1)) @@ -98,15 +108,15 @@ void givenExistingIdWhenGetByIdThenUser() { } @Test - @DisplayName("findById test: try to get user data by not existing id.") + @DisplayName("findById test: try to get task data by not existing id.") void givenNotExistingIdWhenGetByIdThenThrow() { - Long userId = 1L; + Long taskId = 1L; - when(taskRepository.findById(userId)) + when(taskRepository.findById(taskId)) .thenReturn(Optional.empty()); assertThrows(EntityNotFoundException.class, - () -> taskService.findById(userId), + () -> taskService.findById(taskId), " index is incorrect." ); verify(taskRepository, times(1)) @@ -114,9 +124,17 @@ void givenNotExistingIdWhenGetByIdThenThrow() { } @Test - @DisplayName("save test: send user data to repository.") - void givenUserWhenSendUserToDbThenSavedUser() { + @DisplayName("save test: send task data to repository.") + void givenTaskWhenSendTaskToDbThenSavedTask() { Long userPrincipalId = 10L; + User defaultUser = new User( + userPrincipalId, + "username", + "password", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); Task taskToSave = new Task( null, "name", @@ -133,15 +151,7 @@ void givenUserWhenSendUserToDbThenSavedUser() { TaskStatus.DONE, MILLENNIUM, MILLENNIUM, - userPrincipalId - ); - User defaultUser = new User( - userPrincipalId, - "username", - "password", - "email", - RoleType.ROLE_USER, - Collections.emptyList() + defaultUser ); AppUserDetails principal = new AppUserDetails(defaultUser); Authentication auth = @@ -154,6 +164,8 @@ void givenUserWhenSendUserToDbThenSavedUser() { when(securityContext.getAuthentication()).thenReturn(auth); when(taskRepository.save(expected)) .thenReturn(expected); + when(userService.findById(userPrincipalId)) + .thenReturn(defaultUser); Task actual = taskService.save(taskToSave); @@ -163,9 +175,17 @@ void givenUserWhenSendUserToDbThenSavedUser() { } @Test - @DisplayName("update test: with partially filled User data.") - void givenPartiallyFilledUserUpdateThenUpdatedUser() { - Long userId = 1L; + @DisplayName("update test: with partially filled Task data.") + void givenPartiallyFilledTaskUpdateThenUpdatedTask() { + Long taskId = 1L; + User defaultUser = new User( + 2L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); Task partiallyFilledTask = new Task( null, "name", @@ -173,7 +193,7 @@ void givenPartiallyFilledUserUpdateThenUpdatedUser() { TaskStatus.TODO, null, null, - 2L + defaultUser ); Task taskToUpdate = new Task( 1L, @@ -182,7 +202,7 @@ void givenPartiallyFilledUserUpdateThenUpdatedUser() { TaskStatus.DONE, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, - 2L + defaultUser ); Task expected = new Task( 1L, @@ -191,16 +211,18 @@ void givenPartiallyFilledUserUpdateThenUpdatedUser() { TaskStatus.TODO, BEFORE_MILLENNIUM, MILLENNIUM, - 2L + defaultUser ); - when(taskRepository.findById(userId)) + when(taskRepository.findById(taskId)) .thenReturn(Optional.of(taskToUpdate)); when(taskRepository.update(expected)) .thenReturn(expected); + when(userService.findById(2L)) + .thenReturn(defaultUser); - Task actual = taskService.update(userId, partiallyFilledTask); + Task actual = taskService.update(taskId, partiallyFilledTask); assertEquals(expected, actual); verify(taskRepository, times(1)) @@ -210,9 +232,17 @@ void givenPartiallyFilledUserUpdateThenUpdatedUser() { } @Test - @DisplayName("update test: with filled User data.") - void givenFilledUserAndUserIdWhenUpdateThenUpdatedUser() { - Long userId = 1L; + @DisplayName("update test: with filled Task data.") + void givenFilledTaskAndTaskIdWhenUpdateThenUpdatedTask() { + Long taskId = 1L; + User defaultUser = new User( + 2L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); Task taskToUpdate = new Task( 1L, "name", @@ -220,7 +250,7 @@ void givenFilledUserAndUserIdWhenUpdateThenUpdatedUser() { TaskStatus.DONE, BEFORE_MILLENNIUM, BEFORE_MILLENNIUM, - 2L + defaultUser ); Task expected = new Task( 1L, @@ -229,15 +259,17 @@ void givenFilledUserAndUserIdWhenUpdateThenUpdatedUser() { TaskStatus.DONE, BEFORE_MILLENNIUM, MILLENNIUM, - 2L + defaultUser ); - when(taskRepository.findById(userId)) + when(taskRepository.findById(taskId)) .thenReturn(Optional.of(expected)); when(taskRepository.update(expected)) .thenReturn(expected); + when(userService.findById(2L)) + .thenReturn(defaultUser); - Task actual = taskService.update(userId, taskToUpdate); + Task actual = taskService.update(taskId, taskToUpdate); assertEquals(expected, actual); verify(taskRepository, times(1)) @@ -247,10 +279,18 @@ void givenFilledUserAndUserIdWhenUpdateThenUpdatedUser() { } @Test - @DisplayName("update test: try update with not existed user id.") - void givenUserAndNotExistedUserIdWhenUpdateThenUpdatedUser() { - Long notExistedUserId = 1L; + @DisplayName("update test: try update with not existed task id.") + void givenTaskAndNotExistedTaskIdWhenUpdateThenUpdatedTask() { + Long notExistedTaskId = 1L; LocalDateTime creationTime = LocalDateTime.of(1999, Month.DECEMBER, 10, 0, 0, 0); + User defaultUser = new User( + 2L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); Task taskToUpdate = new Task( 1L, "name", @@ -258,15 +298,15 @@ void givenUserAndNotExistedUserIdWhenUpdateThenUpdatedUser() { TaskStatus.DONE, creationTime, creationTime, - 2L + defaultUser ); - when(taskRepository.findById(notExistedUserId)) + when(taskRepository.findById(notExistedTaskId)) .thenReturn(Optional.empty()); assertThrows(EntityNotFoundException.class, - () -> taskService.update(notExistedUserId, taskToUpdate), - "UserId is incorrect." + () -> taskService.update(notExistedTaskId, taskToUpdate), + "TaskId is incorrect." ); verify(taskRepository, times(0)) .update(any()); @@ -275,32 +315,48 @@ void givenUserAndNotExistedUserIdWhenUpdateThenUpdatedUser() { } @Test - @DisplayName("delete test: delete user data message to repository.") - void givenExistedUserIdWhenDeleteThenVoid() { + @DisplayName("delete test: delete task data message to repository.") + void givenExistedTaskIdWhenDeleteThenVoid() { Long existedTaskId = 1L; - + User defaultUser = new User( + 2L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); + Task expected = new Task( + 1L, + "name", + "description", + TaskStatus.DONE, + BEFORE_MILLENNIUM, + MILLENNIUM, + defaultUser + ); when(taskRepository.findById(existedTaskId)) - .thenReturn(Optional.of(new Task())); + .thenReturn(Optional.of(expected)); taskService.deleteById(existedTaskId); verify(taskRepository, times(1)) .findById(existedTaskId); verify(taskRepository, times(1)) - .deleteById(existedTaskId); + .deleteById(expected); } @Test @DisplayName("delete test: delete task data message to repository.") - void givenNotExistedUserIdWhenDeleteThenVoid() { - Long notExistedUserId = 1L; + void givenNotExistedTaskIdWhenDeleteThenVoid() { + Long notExistedTaskId = 1L; assertThrows(EntityNotFoundException.class, - () -> taskService.deleteById(notExistedUserId), - "UserId is incorrect." + () -> taskService.deleteById(notExistedTaskId), + "TaskId is incorrect." ); verify(taskRepository, times(1)) - .findById(notExistedUserId); + .findById(notExistedTaskId); verify(taskRepository, times(0)) - .deleteById(notExistedUserId); + .deleteById(any()); } } \ No newline at end of file diff --git a/src/test/java/com/emobile/springtodo/service/impl/UserServiceImplTest.java b/src/test/java/com/emobile/springtodo/service/impl/UserServiceImplTest.java index 45c0a174..fb382b0d 100644 --- a/src/test/java/com/emobile/springtodo/service/impl/UserServiceImplTest.java +++ b/src/test/java/com/emobile/springtodo/service/impl/UserServiceImplTest.java @@ -7,12 +7,14 @@ import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.UserRepository; import com.emobile.springtodo.service.TaskService; +import com.emobile.springtodo.util.SessionUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.ArrayList; @@ -34,14 +36,12 @@ class UserServiceImplTest { @Mock private UserRepository userRepository; @Mock - private TaskService taskService; - @Mock private PasswordEncoder passwordEncoder; @BeforeEach void setUp() { - userService = new UserServiceImpl(userRepository, taskService, passwordEncoder); + userService = new UserServiceImpl(userRepository, passwordEncoder); userService.setSelf(userService); } @@ -274,15 +274,23 @@ void givenUserAndNotExistedUserIdWhenUpdateThenUpdatedUser() { @DisplayName("delete test: delete user data message to repository.") void givenExistedUserIdWhenDeleteThenVoid() { Long existedUserId = 1L; + User defaultUser = new User( + 1L, + "user", + "pass", + "email", + RoleType.ROLE_USER, + Collections.emptyList() + ); when(userRepository.findById(existedUserId)) - .thenReturn(Optional.of(new User())); + .thenReturn(Optional.of(defaultUser)); userService.deleteById(existedUserId); verify(userRepository, times(1)) .findById(existedUserId); verify(userRepository, times(1)) - .deleteById(existedUserId); + .deleteById(defaultUser); } @Test @@ -297,6 +305,6 @@ void givenNotExistedUserIdWhenDeleteThenVoid() { verify(userRepository, times(1)) .findById(notExistedUserId); verify(userRepository, times(0)) - .deleteById(notExistedUserId); + .deleteById(any()); } } From 28efbe206b6cd81672e554e927d6909d7c938f4d Mon Sep 17 00:00:00 2001 From: SattarovAA Date: Thu, 10 Apr 2025 16:27:32 +0300 Subject: [PATCH 2/2] minor query update and cleanup --- .../handler/ExceptionHandlerController.java | 4 +- .../emobile/springtodo/model/entity/Task.java | 22 ++++++--- .../emobile/springtodo/model/entity/User.java | 14 ++---- .../repository/impl/TaskRepositoryImpl.java | 19 ++------ .../repository/impl/UserRepositoryImpl.java | 48 ++++++++----------- 5 files changed, 49 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java b/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java index 74473bc7..0246aa76 100644 --- a/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java +++ b/src/main/java/com/emobile/springtodo/controller/handler/ExceptionHandlerController.java @@ -80,9 +80,9 @@ public ResponseEntity badRequest( } /** - * ExceptionHandler for {@link AlreadyExitsException}. + * ExceptionHandler for {@link TransactionException}. * - * @param ex exception type of {@link AlreadyExitsException}. + * @param ex exception type of {@link TransactionException}. * @param webRequest web request for exception description. * @return {@link ResponseEntity} with {@link ErrorResponseBody}. * @see #buildResponse(HttpStatus, Exception, WebRequest) diff --git a/src/main/java/com/emobile/springtodo/model/entity/Task.java b/src/main/java/com/emobile/springtodo/model/entity/Task.java index 46f34de1..36c61fc3 100644 --- a/src/main/java/com/emobile/springtodo/model/entity/Task.java +++ b/src/main/java/com/emobile/springtodo/model/entity/Task.java @@ -1,7 +1,21 @@ package com.emobile.springtodo.model.entity; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; import lombok.experimental.FieldNameConstants; import java.io.Serial; @@ -18,10 +32,6 @@ @Getter @FieldNameConstants @Builder -//@NamedEntityGraph( -// name = "Task.withAuthor", -// attributeNodes = @NamedAttributeNode("author") -//) @Entity @Table(name = "tasks") public class Task implements Serializable { diff --git a/src/main/java/com/emobile/springtodo/model/entity/User.java b/src/main/java/com/emobile/springtodo/model/entity/User.java index 147541d0..0e93fe10 100644 --- a/src/main/java/com/emobile/springtodo/model/entity/User.java +++ b/src/main/java/com/emobile/springtodo/model/entity/User.java @@ -35,10 +35,6 @@ @Getter @FieldNameConstants @Builder -//@NamedEntityGraph( -// name = "User.withTasks", -// attributeNodes = @NamedAttributeNode("taskList") -//) @Entity @Table(name = "users", indexes = { @@ -83,9 +79,9 @@ public class User implements Serializable { @OneToMany(mappedBy = Task.Fields.author, orphanRemoval = true) @Builder.Default private List taskList = new ArrayList<>(); - - @ToString.Include - public String getTaskListSize() { - return String.valueOf(taskList.size()); - } +// +// @ToString.Include +// public String getTaskListSize() { +// return String.valueOf(taskList.size()); +// } } diff --git a/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java index a19e246d..d19a530e 100644 --- a/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/impl/TaskRepositoryImpl.java @@ -2,17 +2,14 @@ import com.emobile.springtodo.aop.logger.LazyLogger; import com.emobile.springtodo.model.entity.Task; -import com.emobile.springtodo.model.entity.User; import com.emobile.springtodo.model.util.Page; import com.emobile.springtodo.model.util.PageInfo; import com.emobile.springtodo.repository.TaskRepository; import com.emobile.springtodo.util.SessionUtil; import lombok.RequiredArgsConstructor; -import org.hibernate.Session; import org.hibernate.query.Query; import org.springframework.stereotype.Repository; -import java.util.Objects; import java.util.Optional; @RequiredArgsConstructor @@ -24,11 +21,10 @@ public class TaskRepositoryImpl implements TaskRepository { @LazyLogger public Page findAll(PageInfo pageinfo) { int beginInd = pageinfo.pageNumber() * pageinfo.pageSize(); - int endInd = beginInd + pageinfo.pageSize(); Query query = sessionUtil.getSession() .createQuery("FROM Task", Task.class) .setFirstResult(beginInd) - .setMaxResults(endInd); + .setMaxResults(pageinfo.pageSize()); return new Page<>(query.getResultList()); } @@ -50,12 +46,7 @@ public Task save(Task model) { @Override @LazyLogger public Task update(Task model) { - Session session = sessionUtil.getSession(); - if (Objects.isNull(session.find(User.class, model.getId()))) { - session.persist(model); - return model; - } - return session.merge(model); + return sessionUtil.getSession().merge(model); } @Override @@ -67,9 +58,9 @@ public void deleteById(Task model) { @Override @LazyLogger public void deleteAll() { - Query query = sessionUtil.getSession() - .createQuery("DELETE FROM Task", Task.class); - query.executeUpdate(); + sessionUtil.getSession() + .createQuery("DELETE FROM Task", Task.class) + .executeUpdate(); } @Override diff --git a/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java b/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java index 9db5c2e1..91a08d0f 100644 --- a/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java +++ b/src/main/java/com/emobile/springtodo/repository/impl/UserRepositoryImpl.java @@ -7,11 +7,9 @@ import com.emobile.springtodo.repository.UserRepository; import com.emobile.springtodo.util.SessionUtil; import lombok.RequiredArgsConstructor; -import org.hibernate.Session; import org.hibernate.query.Query; import org.springframework.stereotype.Repository; -import java.util.Objects; import java.util.Optional; @@ -24,20 +22,20 @@ public class UserRepositoryImpl implements UserRepository { @LazyLogger public Page findAll(PageInfo pageinfo) { int beginInd = pageinfo.pageNumber() * pageinfo.pageSize(); - int endInd = beginInd + pageinfo.pageSize(); Query query = sessionUtil.getSession() .createQuery("FROM User", User.class) .setFirstResult(beginInd) - .setMaxResults(endInd); + .setMaxResults(pageinfo.pageSize()); return new Page<>(query.getResultList()); } @Override @LazyLogger public Optional findById(Long id) { - return Optional.ofNullable( - sessionUtil.getSession().get(User.class, id) - ); + Query query = sessionUtil.getSession() + .createQuery("FROM User u LEFT JOIN FETCH u.taskList WHERE u.id = :id", User.class) + .setParameter("id", id); + return Optional.ofNullable(query.uniqueResult()); } @Override @@ -50,12 +48,7 @@ public User save(User model) { @Override @LazyLogger public User update(User model) { - Session session = sessionUtil.getSession(); - if (Objects.isNull(session.find(User.class, model.getId()))) {//TODO toUpdate - session.persist(model); - return model; - } - return session.merge(model); + return sessionUtil.getSession().merge(model); } @Override @@ -68,8 +61,8 @@ public void deleteById(User model) { @LazyLogger public Optional findByUsername(String username) { Query query = sessionUtil.getSession() - .createQuery("FROM User WHERE username = :username", User.class); - query.setParameter("username", username); + .createQuery("FROM User WHERE username = :username", User.class) + .setParameter("username", username); return Optional.ofNullable(query.uniqueResult()); } @@ -99,11 +92,12 @@ public boolean existsByEmailAndIdNot(String email, Long currentUserId) { private boolean existsByField(String fieldName, Object value) { try { - Session session = sessionUtil.getSession(); - Query query = session.createQuery( - "SELECT COUNT(u.id) FROM User u WHERE u." + fieldName + " = :value", - Long.class); - query.setParameter("value", value); + Query query = sessionUtil.getSession() + .createQuery( + "SELECT COUNT(u.id) FROM User u WHERE u." + fieldName + " = :value", + Long.class + ) + .setParameter("value", value); Long count = query.uniqueResult(); return count != null && count > 0; } catch (Exception e) { @@ -114,13 +108,13 @@ private boolean existsByField(String fieldName, Object value) { private boolean existsByField(String fieldName, Object value, Long currentUserId) { try { - Session session = sessionUtil.getSession(); - Query query = session.createQuery( - "SELECT COUNT(u.id) FROM User u WHERE u." + fieldName + " = :value " - + "and u.id != :userId", - Long.class); - query.setParameter("value", value); - query.setParameter("userId", currentUserId); + Query query = sessionUtil.getSession() + .createQuery( + "SELECT COUNT(u.id) FROM User u " + + "WHERE u." + fieldName + " = :value and u.id != :userId", + Long.class) + .setParameter("value", value) + .setParameter("userId", currentUserId); Long count = query.uniqueResult(); return count != null && count > 0; } catch (Exception e) {