Skip to content

Commit b835c34

Browse files
committed
GP-125 migrate from completed branch
1 parent f498d17 commit b835c34

File tree

14 files changed

+401
-0
lines changed

14 files changed

+401
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=50/>Query helper exercise :muscle:
2+
Improve your Hibernate "dirty checking" skills
3+
### Task
4+
`QueryHelper.java` is a simple API that provides a util method helps to perform read operations using `EntityManager`.
5+
Your job is to **implement the *todo* section**. The purpose of this exercise is to learn how to disable *dirty checking*.
6+
7+
To verify your implementation, run `QueryHelperTest.java`
8+
9+
10+
### Pre-conditions :heavy_exclamation_mark:
11+
You're supposed to be familiar with *JPA* and *Hibernate ORM*
12+
13+
### How to start :question:
14+
* Just clone the repository and start implementing the **todo** section, verify your changes by running tests
15+
* If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source)
16+
* Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation
17+
18+
### Related materials :information_source:
19+
* ["Dirty checking" tutorial](https://github.com/bobocode-projects/jpa-hibernate-tutorial/tree/master/dirty-checking-mechanism) <img src="https://raw.githubusercontent.com/bobocode-projects/resources/master/image/logo_transparent_background.png" height=20/>
20+
21+
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>3-0-jpa-and-hibernate</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>3-0-2-query-helper</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>com.bobocode</groupId>
17+
<artifactId>jpa-hibernate-util</artifactId>
18+
<version>1.0-SNAPSHOT</version>
19+
</dependency>
20+
</dependencies>
21+
22+
23+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.bobocode;
2+
3+
import com.bobocode.exception.QueryHelperException;
4+
import org.hibernate.Session;
5+
6+
import javax.persistence.EntityManager;
7+
import javax.persistence.EntityManagerFactory;
8+
import java.util.Collection;
9+
import java.util.function.Function;
10+
11+
/**
12+
* {@link QueryHelper} provides a util method that allows to perform read operations in the scope of transaction
13+
*/
14+
public class QueryHelper {
15+
private EntityManagerFactory entityManagerFactory;
16+
17+
public QueryHelper(EntityManagerFactory entityManagerFactory) {
18+
this.entityManagerFactory = entityManagerFactory;
19+
}
20+
21+
/**
22+
* Receives a {@link Function<EntityManager, T>}, creates {@link EntityManager} instance, starts transaction,
23+
* performs received function and commits the transaction, in case of exception in rollbacks the transaction and
24+
* throws a {@link QueryHelperException} with the following message: "Error performing query. Transaction is rolled back"
25+
* <p>
26+
* The purpose of this method is to perform read operations using {@link EntityManager}, so it uses read only mode
27+
* by default.
28+
*
29+
* @param entityManagerConsumer query logic encapsulated as function that receives entity manager and returns result
30+
* @param <T> generic type that allows to specify single entity class of some collection
31+
* @return query result specified by type T
32+
*/
33+
public <T> T readWithinTx(Function<EntityManager, T> entityManagerConsumer) {
34+
EntityManager entityManager = entityManagerFactory.createEntityManager();
35+
entityManager.unwrap(Session.class).setDefaultReadOnly(true);
36+
entityManager.getTransaction().begin();
37+
try {
38+
T result = entityManagerConsumer.apply(entityManager);
39+
entityManager.getTransaction().commit();
40+
return result;
41+
} catch (Exception e) {
42+
entityManager.getTransaction().rollback();
43+
throw new QueryHelperException("Transaction is rolled back.", e);
44+
} finally {
45+
entityManager.close();
46+
}
47+
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.bobocode.exception;
2+
3+
public class QueryHelperException extends RuntimeException {
4+
public QueryHelperException(String message, Throwable cause) {
5+
super(message, cause);
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
3+
4+
<persistence-unit name="Account">
5+
<class>com.bobocode.model.Account</class>
6+
7+
<properties>
8+
<property name="hibernate.connection.url" value="jdbc:h2:mem:movie_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false"/>
9+
<property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
10+
<property name="hibernate.connection.username" value="movie_user"/>
11+
<property name="hibernate.connection.password" value="movie_pass"/>
12+
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
13+
<property name="hibernate.hbm2ddl.auto" value="create"/>
14+
</properties>
15+
</persistence-unit>
16+
17+
</persistence>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.bobocode;
2+
3+
import com.bobocode.exception.QueryHelperException;
4+
import com.bobocode.model.Account;
5+
import com.bobocode.util.EntityManagerUtil;
6+
import com.bobocode.util.TestDataGenerator;
7+
import org.junit.jupiter.api.AfterAll;
8+
import org.junit.jupiter.api.BeforeAll;
9+
import org.junit.jupiter.api.Test;
10+
11+
import javax.persistence.EntityManagerFactory;
12+
import javax.persistence.Persistence;
13+
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.hamcrest.Matchers.*;
16+
import static org.junit.jupiter.api.Assertions.fail;
17+
18+
19+
public class QueryHelperTest {
20+
private static EntityManagerFactory entityManagerFactory;
21+
private static EntityManagerUtil emUtil;
22+
private static QueryHelper queryHelper;
23+
24+
@BeforeAll
25+
public static void setup() {
26+
entityManagerFactory = Persistence.createEntityManagerFactory("Account");
27+
emUtil = new EntityManagerUtil(entityManagerFactory);
28+
queryHelper = new QueryHelper(entityManagerFactory);
29+
}
30+
31+
@AfterAll
32+
static void destroy() {
33+
entityManagerFactory.close();
34+
}
35+
36+
@Test
37+
public void testQueryHelperReturnsResult() {
38+
Account account = saveRandomAccount();
39+
Long accountId = account.getId();
40+
41+
Account foundAccount = queryHelper.readWithinTx(entityManager -> entityManager.find(Account.class, accountId));
42+
43+
assertThat(foundAccount, notNullValue());
44+
}
45+
46+
@Test
47+
public void testQueryHelperUsesReadOnly() {
48+
Account account = saveRandomAccount();
49+
Long accountId = account.getId();
50+
51+
tryToUpdateFirstName(accountId);
52+
Account foundAccount = queryHelper.readWithinTx(entityManager -> entityManager.find(Account.class, accountId));
53+
54+
assertThat(foundAccount.getFirstName(), not(equalTo("XXX")));
55+
assertThat(foundAccount.getFirstName(), equalTo(account.getFirstName()));
56+
}
57+
58+
private Account saveRandomAccount() {
59+
Account account = TestDataGenerator.generateAccount();
60+
emUtil.performWithinTx(entityManager -> entityManager.persist(account));
61+
return account;
62+
}
63+
64+
private void tryToUpdateFirstName(Long accountId) {
65+
queryHelper.readWithinTx(entityManager -> {
66+
Account managedAccount = entityManager.find(Account.class, accountId);
67+
managedAccount.setFirstName("XXX");
68+
return managedAccount;
69+
});
70+
}
71+
72+
73+
@Test
74+
public void testQueryHelperThrowsException() {
75+
Account account = TestDataGenerator.generateAccount();
76+
emUtil.performWithinTx(entityManager -> entityManager.persist(account));
77+
try {
78+
queryHelper.readWithinTx(entityManager -> {
79+
Account managedAccount = entityManager.find(Account.class, account.getId());
80+
throwException();
81+
return managedAccount;
82+
});
83+
fail("Exception should be thrown");
84+
} catch (Exception e) {
85+
assertThat(e.getClass(), equalTo(QueryHelperException.class));
86+
assertThat(e.getMessage(), containsString("Transaction is rolled back"));
87+
}
88+
}
89+
90+
private void throwException() {
91+
throw new RuntimeException("Runtime error");
92+
}
93+
}

3-0-jpa-and-hibernate/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<modules>
1616
<module>3-0-0-hello-jpa-entity</module>
1717
<module>3-0-1-hello-persistence-xml</module>
18+
<module>3-0-2-query-helper</module>
1819
</modules>
1920

2021
<dependencies>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>java-persistence-util</artifactId>
7+
<groupId>com.bobocode</groupId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>jpa-hibernate-model</artifactId>
13+
14+
15+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.bobocode.model;
2+
3+
import lombok.*;
4+
5+
import javax.persistence.*;
6+
import java.math.BigDecimal;
7+
import java.time.LocalDate;
8+
import java.time.LocalDateTime;
9+
10+
@NoArgsConstructor
11+
@Getter
12+
@Setter
13+
@ToString
14+
@EqualsAndHashCode(of = "id")
15+
@Entity
16+
@Table(name = "account")
17+
public class Account {
18+
@Id
19+
@GeneratedValue(strategy = GenerationType.IDENTITY)
20+
private Long id;
21+
22+
@Column(name = "first_name", nullable = false)
23+
private String firstName;
24+
25+
@Column(name = "last_name", nullable = false)
26+
private String lastName;
27+
28+
@Column(name = "email", nullable = false)
29+
private String email;
30+
31+
@Column(name = "birthday", nullable = false)
32+
private LocalDate birthday;
33+
34+
@Column(name = "gender", nullable = false)
35+
@Enumerated(EnumType.STRING)
36+
private Gender gender;
37+
38+
@Column(name = "creation_time", nullable = false)
39+
private LocalDateTime creationTime;
40+
41+
@Column(name = "balance")
42+
private BigDecimal balance = BigDecimal.ZERO.setScale(2);
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.bobocode.model;
2+
3+
public enum Gender {
4+
MALE,
5+
FEMALE
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.bobocode</groupId>
9+
<artifactId>java-persistence-util</artifactId>
10+
<version>1.0-SNAPSHOT</version>
11+
</parent>
12+
<artifactId>jpa-hibernate-util</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>io.codearte.jfairy</groupId>
17+
<artifactId>jfairy</artifactId>
18+
<version>0.5.7</version>
19+
</dependency>
20+
<dependency>
21+
<groupId>com.bobocode</groupId>
22+
<artifactId>jpa-hibernate-model</artifactId>
23+
<version>1.0-SNAPSHOT</version>
24+
</dependency>
25+
</dependencies>
26+
27+
28+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.bobocode.util;
2+
3+
import javax.persistence.EntityManager;
4+
import javax.persistence.EntityManagerFactory;
5+
import java.util.function.Consumer;
6+
import java.util.function.Function;
7+
8+
public class EntityManagerUtil {
9+
private EntityManagerFactory entityManagerFactory;
10+
11+
public EntityManagerUtil(EntityManagerFactory entityManagerFactory) {
12+
this.entityManagerFactory = entityManagerFactory;
13+
}
14+
15+
public void performWithinTx(Consumer<EntityManager> entityManagerConsumer) {
16+
EntityManager entityManager = entityManagerFactory.createEntityManager();
17+
entityManager.getTransaction().begin();
18+
try {
19+
entityManagerConsumer.accept(entityManager);
20+
entityManager.getTransaction().commit();
21+
} catch (Exception e) {
22+
entityManager.getTransaction().rollback();
23+
throw e;
24+
} finally {
25+
entityManager.close();
26+
}
27+
}
28+
29+
public <T> T performReturningWithinTx(Function<EntityManager, T> entityManagerFunction) {
30+
EntityManager entityManager = entityManagerFactory.createEntityManager();
31+
entityManager.getTransaction().begin();
32+
try {
33+
T result = entityManagerFunction.apply(entityManager);
34+
entityManager.getTransaction().commit();
35+
return result;
36+
} catch (Exception e) {
37+
entityManager.getTransaction().rollback();
38+
throw e;
39+
} finally {
40+
entityManager.close();
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)