Showing posts with label enum. Show all posts
Showing posts with label enum. Show all posts

Sunday, March 03, 2024

Enums - all the way to persistence (revisited and revised for today's tech stack) . . .

About two years ago I blogged on this combination: Enums - all the way to persistence. Technology is moving at faster pace than ever before. Java's release cadence is moving at rapid 6 months cycle, every March and September. Spring boot catches Java and other technologies and moves along at the same pace as Java, every 6 months in May and November. Of course, the PostgreSQL database, Hibernate and even Maven build system keep moving as well, at their own pace.

The challenge for Java developer is to keep up with all the moving technologies. As software development requires talented developers with years of experience and knowledge, debates go on Artificial Intelligence (AI). Some who have moved away from coding strongly feel that the generative AI which currently is capable of generating even code will replace software developers. I don't believe in that, at least at this time. The add-on 'at least at this time' is only a cautious extension to that non-generative human statement). I have tried CodeGPT lately at work, a couple of times when I was stuck with things not working together as described in documentations and blog posts, asking it's generative trained intelligence to see if it would be able to be my copilot in those development situations. It couldn't really stand up the hype in anyway, and I had to go and figure out myself all those situations.

Enums persistence - is one such problem again I did hit roadblocks lately after two years. The only change is newer versions of all of these technologies. It required additional exploration of few things before arriving at a solution that worked eventually.

Environment: Java 21, Spring Boot 3.2.3, PostgreSQL 15.3, maven 3.9.6 on macOS Catalina 10.15.7

Spring boot 3.2.3 data jpa brings in Hibernate 6.4.4 dependency.

The same persistent model described in my earlier blog post: Enums - all the way to persistence would need the following changes for enums to work.

The DDL script requires an extra PostgreSQL casting as shown below for enums:

-- create enum type genders CREATE TYPE genders AS ENUM( 'MALE', 'FEMALE' ); CREATE CAST (varchar AS genders) WITH INOUT AS IMPLICIT;

In maven pom.xml, the spring boot version is 3.2.3 and the hibernate-types-55 dependency is not needed.

Changes to domain object Person.java are shown below (the @TypeDef annotation is not required):

... import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; ... @NotNull @Enumerated(EnumType.STRING) @JdbcTypeCode(SqlTypes.NAMED_ENUM) Gender gender; ... }

Changes look simple after figuring out and making things to work, but finding things that work required a bit of exploration ;)

References

Thursday, September 22, 2022

Enums - all the way to persistence . . .

In any application there would be a need for pre-defined ordered set of constants. Enum is a data type good for such cases. Java has enum since 1.5. Databases do support enum type and PostgreSQL has a special support for this since release 8.3. JPA and Spring Data is a good match to use in modern Java applications for persistence, especially in Spring Boot applications. 

Environment: Java 17, Spring Boot 2.6,7 on macOS Catalina 10.15.7

Example Scenario - A persistable entity object in a Spring Boot micro-service application with JPA and PostgreSQL DB.

DDL Script
-- create enum type genders CREATE TYPE genders AS ENUM( 'MALE', 'FEMALE' ); -- create people table CREATE TABLE people( id VARCHAR(36) PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, gender genders NOT NULL ); -- Unique Constraints ALTER TABLE people ADD CONSTRAINT people_fname_lname_uk UNIQUE (first_name, last_name);

Maven dependencies: pom.xml
... <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-55</artifactId> <version>2.16.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> <scope>provided</scope> </dependency> ... </dependencies> ...

Enum: Gender.java
import lombok.AllArgsConstructor; @AllArgsConstructor public enum Gender { MALE("Male"), FEMALE("Female"); String genderName; }

Domain Object: Person.java
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.UniqueConstraint; import javax.validation.constraints.NotNull; import java.util.UUID; @Entity @Table( name = "people", uniqueConstraints = { @UniqueConstraint( columnNames = {"firstName", "lastName"}, name = "people_fname_lname_uk" ) } ) @TypeDef( name = "pgsql_enum", typeClass = PostgreSQLEnumType.class ) @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(length = 36, nullable = false, updatable = false) @Type(type="org.hibernate.type.UUIDCharType") private UUID id; @NotNull String firstName; @NotNull String lastName; @NotNull @Enumerated(EnumType.STRING) @Type(type = "pgsql_enum") Gender gender; }

JPA Repository: PersonRepository.java
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.UUID; public interface PersonRepository extends JpaRepository<Person, UUID> { Person findByFirstNameAndLastNameAndGender(String firstName, String lastName, Gender gender); @Query(value = "SELECT * FROM people WHERE first_name = :firstName AND last_name = :lastName AND gender = CAST(:#{#gender.name()} as genders)", nativeQuery = true) Person findByFirstNameAndLastNameAndGenderNQ(String firstName, String lastName, Gender gender); }

JPA Repository Test: PersonRepositoryIT.java
import org.junit.Test; import org.junit.runner.RunWith; 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.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") @RunWith(SpringRunner.class) @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) public class PersonRepositoryIT { @Autowired TestEntityManager testEntityManager; @Autowired PersonRepository personRepository; @Test public void findBy_returns_expected() { // given: data in DB List.of( Person.builder().firstName("Giri").lastName("Potte").gender(Gender.MALE), Person.builder().firstName("Boo").lastName("Potte").gender(Gender.MALE) ).forEach(testEntityManager::persist); testEntityManager.flush(); // expect: assertThat( personRepository.findByFirstNameAndLastNameAndGender("Giri", "Potte", Gender.MALE) ).isNotNull() .extracting("firstName", "lastName", "gender") .isEqualTo(List.of("Giri", "Potte", Gender.MALE)); // expect: assertThat( personRepository.findByFirstNameAndLastNameAndGenderNQ("Boo", "Potte", Gender.MALE) ).isNotNull() .extracting("firstName", "lastName", "gender") .isEqualTo(List.of("Boo", "Potte", Gender.MALE)); } }

TIP

From Database side, sometimes, it's handy to have some SQLs dealing with enum type to create, drop, list values, add a new value, modify existing value etc.

I was just fiddling with PostgreSQL enum type and the following is the link:

References