From 9e0f5b08ec80d10e2c0ef334091b8419db8148e2 Mon Sep 17 00:00:00 2001 From: jez04 <david.jezek@post.cz> Date: Tue, 25 Mar 2025 23:47:38 +0100 Subject: [PATCH] feat: :tada: solution --- src/main/java/lab/App.java | 47 ++- .../java/lab/data/FirstPersonShooter.java | 2 + src/main/java/lab/data/Game.java | 17 + src/main/java/lab/data/PlatformGame.java | 6 + src/main/java/lab/data/Player.java | 19 + src/main/java/lab/data/Score.java | 21 +- src/main/java/lab/storage/JpaConnector.java | 64 +++- src/main/java/module-info.java | 2 +- .../structure/test/ClassStructureTest.java | 324 +++++++++++++++++- 9 files changed, 477 insertions(+), 25 deletions(-) diff --git a/src/main/java/lab/App.java b/src/main/java/lab/App.java index be7b48d..69dd041 100644 --- a/src/main/java/lab/App.java +++ b/src/main/java/lab/App.java @@ -2,9 +2,14 @@ package lab; import java.io.IOException; import java.sql.SQLException; +import java.util.List; import org.h2.tools.Server; +import lab.data.Game; +import lab.data.Player; +import lab.data.Score; +import lab.data.Score.Difficult; import lab.storage.JpaConnector; import lombok.extern.log4j.Log4j2; @@ -23,12 +28,50 @@ public class App { Server server = startDBWebServer(); JpaConnector connector = new JpaConnector(); - - //TODO + for (int i = 0; i < 10; i++) { + connector.save(Game.generateAny()); + } + for (int i = 0; i < 10; i++) { + connector.save(Player.generate()); + } + List<Player> players = connector.getAll(Player.class); + List<Game> games = connector.getAll(Game.class); + for (int i = 0; i < 10; i++) { + connector.save(Score.generate(players, games)); + } + connector.getEntityManager().clear(); + connector.getAll(Score.class).forEach(System.out::println); + connector.getAll(Player.class).forEach(App::printPlayer); + connector.getAll(Game.class).forEach(App::printGame); + + System.out.println("-----------null,null-----------------------------"); + connector.findBy(null, null).forEach(System.out::println); + System.out.println("----------- ,null-----------------------------"); + connector.findBy(" ", null).forEach(System.out::println); + System.out.println("-----------'ea',null-----------------------------"); + connector.findBy("ea", null).forEach(System.out::println); + System.out.println("-----------'ea',medium-----------------------------"); + connector.findBy("ea", Difficult.MEDIUM).forEach(System.out::println); + System.out.println("-----------'Java',null-----------------------------"); + connector.findBy("Java", Difficult.MEDIUM).forEach(System.out::println); waitForKeyPress(); stopDBWebServer(server); } + + private static void printPlayer(Player player) { + System.out.println(player); + for (Score score : player.getScores()) { + System.out.println(" -> " + score); + } + } + + private static void printGame(Game game) { + System.out.println(game); + for (Score score : game.getScores()) { + System.out.println(" -> " + score); + } + } private static Server startDBWebServer() { // Start HTTP server for access H2 DB for look inside diff --git a/src/main/java/lab/data/FirstPersonShooter.java b/src/main/java/lab/data/FirstPersonShooter.java index 62b7b3b..79eca4b 100644 --- a/src/main/java/lab/data/FirstPersonShooter.java +++ b/src/main/java/lab/data/FirstPersonShooter.java @@ -11,8 +11,10 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +@Entity @Getter @Setter +@NoArgsConstructor @ToString(callSuper = true) public class FirstPersonShooter extends Game{ diff --git a/src/main/java/lab/data/Game.java b/src/main/java/lab/data/Game.java index 4bbec16..2f771a6 100644 --- a/src/main/java/lab/data/Game.java +++ b/src/main/java/lab/data/Game.java @@ -2,20 +2,37 @@ package lab.data; import java.util.List; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import lab.Tools; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +@Entity @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Builder(toBuilder = true) public class Game implements MyEntity{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @EqualsAndHashCode.Include private Long id; private String name; + @OneToMany(mappedBy = Score_.GAME) + @ToString.Exclude private List<Score> scores; public static Game generate() { diff --git a/src/main/java/lab/data/PlatformGame.java b/src/main/java/lab/data/PlatformGame.java index 93d1465..e5282ff 100644 --- a/src/main/java/lab/data/PlatformGame.java +++ b/src/main/java/lab/data/PlatformGame.java @@ -2,13 +2,19 @@ package lab.data; import java.util.List; +import jakarta.persistence.Entity; import lab.Tools; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +@Entity @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor @ToString(callSuper = true) public class PlatformGame extends Game{ diff --git a/src/main/java/lab/data/Player.java b/src/main/java/lab/data/Player.java index faedbd3..5753af8 100644 --- a/src/main/java/lab/data/Player.java +++ b/src/main/java/lab/data/Player.java @@ -2,23 +2,42 @@ package lab.data; import java.util.List; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import lab.Tools; +import lab.data.Score.Difficult; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +@Entity @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Builder(toBuilder = true) public class Player implements MyEntity{ + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @EqualsAndHashCode.Include private Long id; private String firstName; private String lastName; private String nick; + @OneToMany(mappedBy = Score_.PLAYER) + @ToString.Exclude private List<Score> scores; public static Player generate() { diff --git a/src/main/java/lab/data/Score.java b/src/main/java/lab/data/Score.java index 314677e..65a2297 100644 --- a/src/main/java/lab/data/Score.java +++ b/src/main/java/lab/data/Score.java @@ -1,26 +1,45 @@ package lab.data; import java.util.List; +import java.util.Random; +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.ManyToOne; +import jakarta.persistence.OneToMany; import lab.Tools; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +@Entity @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString @Builder(toBuilder = true) public class Score implements MyEntity{ + @EqualsAndHashCode.Include + @Id + @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private int points; @Enumerated(EnumType.STRING) - private Difficult level; + private Difficult difficult; + @ManyToOne private Player player; + @ManyToOne private Game game; diff --git a/src/main/java/lab/storage/JpaConnector.java b/src/main/java/lab/storage/JpaConnector.java index 2115152..bf7b2ee 100644 --- a/src/main/java/lab/storage/JpaConnector.java +++ b/src/main/java/lab/storage/JpaConnector.java @@ -1,13 +1,20 @@ package lab.storage; +import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.Persistence; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import lab.data.Game_; import lab.data.MyEntity; import lab.data.Score; +import lab.data.Score_; public class JpaConnector { @@ -19,18 +26,29 @@ public class JpaConnector { em = emf.createEntityManager(); } - public<T extends MyEntity> List<T> getAll(Class<T> clazz) { - return null; + public <T extends MyEntity> List<T> getAll(Class<T> clazz) { + return em.createQuery("select e from " + clazz.getSimpleName() + " e", clazz).getResultList(); } - public void init() { - } - - public<T extends MyEntity> T save(T score) { - return null; + public <T extends MyEntity> T save(T entity) { + T result; + em.getTransaction().begin(); + if (entity.getId() == null || entity.getId() == 0) { + em.persist(entity); + result = entity; + } else { + result = em.merge(entity); + } + em.getTransaction().commit(); + return result; } - public void delete(List<? extends MyEntity> e) { + public void delete(List<? extends MyEntity> entities) { + em.getTransaction().begin(); + for (MyEntity entity : entities) { + em.remove(entity); + } + em.getTransaction().commit(); } public void stop() { @@ -39,24 +57,36 @@ public class JpaConnector { } public EntityManager getEntityManager() { - //return entity manager. Type Object is there because of compilation of empty task assignment return em; } - public<T> T find(long id, Class<T> clazz) { + public <T> T find(long id, Class<T> clazz) { return em.find(clazz, id); } - - public void modifyNoPersistOrMerge(long id, Consumer<Score> motificator) { + + public void modifyNoPersistOrMerge(long id, Consumer<Score> modificator) { em.getTransaction().begin(); Score score = em.find(Score.class, id); - motificator.accept(score); + modificator.accept(score); em.getTransaction().commit(); } - - public List<Score> findBy(String partialName, Score.Difficult difficult){ - return null; + public List<Score> findBy(String partialName, Score.Difficult difficult) { + CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + CriteriaQuery<Score> criteriaQuery = criteriaBuilder.createQuery(Score.class); + Root<Score> root = criteriaQuery.from(Score.class); + List<Predicate> predicates = new ArrayList<>(); + if (partialName != null && !partialName.isBlank()) { + predicates.add( + criteriaBuilder.like(root.get(Score_.game).get(Game_.name), String.format("%%%s%%", partialName))); + } + if (difficult != null) { + predicates.add(criteriaBuilder.equal(root.get(Score_.difficult), difficult)); + } + if (!predicates.isEmpty()) { + criteriaQuery.where(predicates.toArray(new Predicate[predicates.size()])); + } + return em.createQuery(criteriaQuery).getResultList(); } - + } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 60e43a3..97fd165 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -5,7 +5,7 @@ module cz.vsb.fei.java2.lab06_module { requires jakarta.persistence; requires com.h2database; requires org.hibernate.orm.core; - requires jakarta.annotation; + requires static jakarta.annotation; opens lab.data; diff --git a/src/test/java/jez04/structure/test/ClassStructureTest.java b/src/test/java/jez04/structure/test/ClassStructureTest.java index b850b1e..3b9e02e 100644 --- a/src/test/java/jez04/structure/test/ClassStructureTest.java +++ b/src/test/java/jez04/structure/test/ClassStructureTest.java @@ -1,23 +1,87 @@ package jez04.structure.test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import cz.vsb.fei.kelvin.unittest.ProjectContains; import cz.vsb.fei.kelvin.unittest.StructureHelper; import cz.vsb.fei.kelvin.unittest.TextFileContains; import cz.vsb.fei.kelvin.unittest.XmlFileContains; +import lab.data.FirstPersonShooter; +import lab.data.Game; +import lab.data.MyEntity; +import lab.data.PlatformGame; +import lab.data.Player; +import lab.data.Score; +import lab.data.Score.Difficult; +import lab.storage.JpaConnector; class ClassStructureTest { StructureHelper helper = StructureHelper.getInstance(ClassStructureTest.class); + @ParameterizedTest + @CsvSource({ + "Score.java,@Entity", + "Score.java,@NoArgsConstructor", + "Score.java,@EqualsAndHashCode", + "Score.java,@EqualsAndHashCode\\(onlyExplicitlyIncluded\\s*=\\s*true\\)", + "Score.java,@EqualsAndHashCode\\.Include", + "Score.java,@ManyToOne", + "Game.java,@Entity", + "Game.java,@NoArgsConstructor", + "Game.java,@EqualsAndHashCode", + "Game.java,@EqualsAndHashCode\\(onlyExplicitlyIncluded\\s*=\\s*true\\)", + "Game.java,@EqualsAndHashCode\\.Include", + "Game.java,@OneToMany\\(mappedBy", + "Game.java,@ToString\\.Exclude", + "Player.java,@Entity", + "Player.java,@NoArgsConstructor", + "Player.java,@EqualsAndHashCode", + "Player.java,@EqualsAndHashCode\\(onlyExplicitlyIncluded\\s*=\\s*true\\)", + "Player.java,@EqualsAndHashCode\\.Include", + "Player.java,@OneToMany\\(mappedBy", + "Player.java,@ToString\\.Exclude", + "FirstPersonShooter.java,@Entity", + "FirstPersonShooter.java,@NoArgsConstructor", + "PlatformGame.java,@Entity", + "PlatformGame.java,@NoArgsConstructor", + }) + void anotaceTest(String file, String annotation) throws URISyntaxException { + assertThat(TextFileContains.getProjectRoot(getClass()), + new TextFileContains(file, annotation)); + } + + @ParameterizedTest + @CsvSource({ + "Game.java,Score_.GAME", + "Player.java, Score_.PLAYER", + }) + void mappedByTest(String file, String annotation) throws URISyntaxException { + assertThat(TextFileContains.getProjectRoot(getClass()), + new TextFileContains(file, annotation)); + } + @Test void jakartaAndHibernateAsDependencyTest() throws URISyntaxException { assertThat(TextFileContains.getProjectRoot(getClass()), new XmlFileContains("pom.xml", @@ -44,10 +108,262 @@ class ClassStructureTest { Matchers.endsWith(Paths.get("resources", "META-INF", "persistence.xml").toString())); } - @Test - void useEnumeratedTest() throws URISyntaxException { - assertThat(TextFileContains.getProjectRoot(getClass()), - new TextFileContains("Score.java", "@Enumerated\\(\\s*EnumType.STRING\\s*\\)")); + @TestMethodOrder(OrderAnnotation.class) + @Nested + class JpaConnectorTests { + + private Player player; + private Game game; + private PlatformGame platformGame; + private FirstPersonShooter firstPersonShooter; + private static JpaConnector connector; + + @BeforeAll + static void initAll() { + try { + connector = new JpaConnector(); + } catch (Exception e) { + e.printStackTrace(); + } + } + @BeforeEach + void init() { + player = Player.generate(); + game = Game.generate(); + platformGame = PlatformGame.generate(); + firstPersonShooter = FirstPersonShooter.generate(); + connector.getEntityManager().clear(); + if(connector.getEntityManager().getTransaction().isActive()) { + connector.getEntityManager().getTransaction().rollback(); + } + } + + @AfterAll + static void cleanUpAll() { + connector.stop(); + } + + boolean same(MyEntity e1, MyEntity e2) { + if(e1 instanceof Player p1 && e2 instanceof Player p2) { + return same(p1, p2); + } else if(e1 instanceof PlatformGame pg1 && e2 instanceof PlatformGame pg2) { + return same(pg1, pg2); + } else if(e1 instanceof FirstPersonShooter fps1 && e2 instanceof FirstPersonShooter fps2) { + return same(fps1, fps2); + } else if(e1 instanceof Game g1 && e2 instanceof Game g2) { + return same(g1, g2); + } else if(e1 instanceof Score s1 && e2 instanceof Score s2) { + return same(s1, s2); + } + return false; + } + + private boolean same(Score s1, Score s2) { + return s1.getDifficult() == s2.getDifficult() + && s1.getPoints() == s2.getPoints() + && Objects.equals(s1.getId(), s2.getId()) + && Objects.equals(s1.getPlayer().getId(), s2.getPlayer().getId()) + && Objects.equals(s1.getGame().getId(), s2.getGame().getId()); + } + + private boolean same(Player p1, Player p2) { + return Objects.equals(p1.getId(), p2.getId()) + && Objects.equals(p1.getFirstName(), p2.getFirstName()) + && Objects.equals(p1.getLastName(), p2.getLastName()) + && Objects.equals(p1.getNick(), p2.getNick()) + && same(p1.getScores(), p2.getScores()); + } + private boolean same(Game g1, Game g2) { + return Objects.equals(g1.getId(), g2.getId()) + && Objects.equals(g1.getName(), g2.getName()) + && same(g1.getScores(), g2.getScores()); + } + private boolean same(PlatformGame g1, PlatformGame g2) { + return same((Game)g1, (Game)g2) + && Objects.equals(g1.getPlatformType(), g2.getPlatformType()) + && g1.isRetro() == g2.isRetro(); + } + private boolean same(FirstPersonShooter g1, FirstPersonShooter g2) { + return same((Game)g1, (Game)g2) + && Objects.equals(g1.getFpsType(), g2.getFpsType()) + && g1.getPlayersCount() == g2.getPlayersCount(); + } + private boolean same(List<?> l1, List<?> l2) { + if(l1 == null) { + l1 = Collections.emptyList(); + } + if(l2 == null) { + l2 = Collections.emptyList(); + } + return Objects.equals(l1, l2); + } + + private void saveAndReadSame(MyEntity original) { + MyEntity result = connector.save(original); + assertThat(result.getId(), Matchers.notNullValue()); + connector.getEntityManager().clear(); + MyEntity saved = connector.getEntityManager().find(result.getClass(), result.getId()); + assertTrue(same(result, saved), "Saved and readed entities are not same."); + } + + @Test + @Order(100) + void jpaPlayerInsertTest() { + saveAndReadSame(player); + } + + @Test + @Order(120) + void jpaGameInsertTest() { + saveAndReadSame(game); + } + + @Test + @Order(130) + void jpaPlatformGameInsertTest() { + saveAndReadSame(platformGame); + } + + @Test + @Order(140) + void jpaFirstPersonShooterInsertTest() { + saveAndReadSame(firstPersonShooter); + } + + @Test + @Order(150) + void jpaScoreInsertTest() { + saveAndReadSame(Score.generate(connector.getAll(Player.class), connector.getAll(Game.class))); + } + + @Test + @Order(200) + void jpaPlayerReadTest() { + List<Player> savedEntities = connector.getAll(Player.class).stream().toList(); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + } + + @Test + @Order(210) + void jpaGameReadTest() { + List<Game> savedEntities = connector.getAll(Game.class).stream().toList(); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + } + @Test + @Order(220) + void jpaPlatformGameReadTest() { + List<PlatformGame> savedEntities = connector.getAll(PlatformGame.class).stream().toList(); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + } + @Test + @Order(230) + void jpaFirstPersonShooterReadTest() { + List<FirstPersonShooter> savedEntities = connector.getAll(FirstPersonShooter.class).stream().toList(); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + } + @Test + @Order(240) + void jpaScoreReadTest() { + List<Score> savedEntities = connector.getAll(Score.class).stream().toList(); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + } + + @Test + @Order(250) + void jpaGameScoresContainScoreTest() { + List<Score> savedEntities = connector.getAll(Score.class).stream().toList(); + for (Score score : savedEntities) { + assertThat(score.getGame().getScores(), Matchers.containsInRelativeOrder(score)); + } + } + @Test + @Order(260) + void jpaPlayerScoresContainScoreTest() { + List<Score> savedEntities = connector.getAll(Score.class).stream().toList(); + for (Score score : savedEntities) { + assertThat(score.getPlayer().getScores(), Matchers.containsInRelativeOrder(score)); + } + } + @Test + @Order(270) + void jpaScoreToStringTest() { + List<Score> savedEntities = connector.getAll(Score.class); + for (Score score : savedEntities) { + assertThat(score.toString(), Matchers.notNullValue()); + } + } + @Test + @Order(280) + void jpaPlayerToStringTest() { + List<Player> savedEntities = connector.getAll(Player.class); + for (Player p : savedEntities) { + assertThat(p.toString(), Matchers.notNullValue()); + } + } + + private void deleteAllAndAssert(Class<? extends MyEntity> clazz) { + List<? extends MyEntity> savedEntities = connector.getAll(clazz); + assertThat(savedEntities, Matchers.not(Matchers.hasSize(0))); + connector.delete(savedEntities); + List<? extends MyEntity> modifiedEntities = connector.getAll(clazz); + assertThat(modifiedEntities, Matchers.hasSize(0)); + } + + @Test + @Order(300) + void jpaScoreDeleteTest() { + deleteAllAndAssert(Score.class); + } + @Test + @Order(310) + void jpaPlayerDeleteTest() { + deleteAllAndAssert(Player.class); + } + + @Test + @Order(320) + void jpaGameDeleteTest() { + deleteAllAndAssert(Game.class); + } + + @Test + @Order(400) + void jpaFindByNullNUllTest() { + connector.save(player); + game.setName("aa-test-bb"); + connector.save(game); + Game g2 = new Game(null, "aa--bb", null); + connector.save(g2); + connector.save(new Score(null, 100, Difficult.EASY, player, game)); + connector.save(new Score(null, 100, Difficult.HARD, player, game)); + connector.save(new Score(null, 100, Difficult.EASY, player, g2)); + connector.save(new Score(null, 100, Difficult.HARD, player, g2)); + assertThat(connector.findBy(null, null), Matchers.hasSize(4)); + } + + @Test + @Order(410) + void jpaFindByDiffTest() { + assertThat(connector.findBy(null, Difficult.HARD), Matchers.hasSize(2)); + } + + @Test + @Order(420) + void jpaFindByNameTest() { + assertThat(connector.findBy("test", null), Matchers.hasSize(2)); + } + + @Test + @Order(430) + void jpaFindByNameAndDiffTest() { + assertThat(connector.findBy("test", Difficult.EASY), Matchers.hasSize(1)); + } + + @Test + @Order(440) + void jpaFindByNameAndDiff2Test() { + assertThat(connector.findBy("--", Difficult.HARD), Matchers.hasSize(1)); + } } } -- GitLab