Jokes POJO

TThe Jokes class demonstrates key features of a POJO (Plain Old Java Object) in Java. It uses Lombok annotations to simplify the creation of common methods and JPA annotations for database interactions.

Review target directory to see generated code.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;

@Data  // Annotations to simplify writing code (ie constructors, setters)
@NoArgsConstructor  // Builds zero argument constructor
@AllArgsConstructor // Builds constructor for all agurments
@Entity // Annotation to simplify creating an entity, which is a lightweight persistence domain object. Typically, an entity represents a table in a relational database, and each entity instance corresponds to a row in that table.
public class Jokes {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;  // Unique identifier

    @Column(unique=true)
    private String joke;  // The Joke

    private int haha;  // Store joke likes
    private int boohoo;  // Store joke jeers
}

Peron POJO

Person shows off some of the shortcuts and methods in defining data types. It is well worth time to study these definitions.

Roles (roles):

Annotation: @ManyToMany(fetch = EAGER) Description: This field defines a many-to-many relationship with the PersonRole entity. It uses eager fetching to load the roles immediately when a Person entity is loaded

Stats (stats):

Annotations: @JdbcTypeCode(SqlTypes.JSON), @Column(columnDefinition = “jsonb”) Description: This field stores JSON data in a binary format (jsonb). It is used to store daily statistics for the person.

Custom Getter for Age (getAge):

Description: This method calculates and returns the age of the person based on their date of birth (dob).

Initialization Function (init):

Description: This static method initializes an array list of Person objects with test data. It is useful for setting up initial data for testing and development purposes.

implements Comparable

Description: The Comparable interface and @Override of compareTo method allows objects of Person to use the name field as a key for comparison. Observe the init method that shows Collections.sort on a List of people created from the Person Pojo.

Run Person.java directly

To see that Person is truly a Plain Old Java Object you can run the file in isolation. This will output objects according to Lombok toString implementation.

Search up CommandLineRunner

Initilizing the database occurs as a result of CommandLineRunner being called at startup. This is useful to have database with default rows and columns for testing.

Class Person

package com.nighthawk.spring_portfolio.mvc.person;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Convert;
import static jakarta.persistence.FetchType.EAGER;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;

import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import org.springframework.format.annotation.DateTimeFormat;

import com.vladmihalcea.hibernate.type.json.JsonType;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;

/**
 * Person is a POJO, Plain Old Java Object.
 * --- @Data is Lombox annotation for @Getter @Setter @ToString @EqualsAndHashCode @RequiredArgsConstructor
 * --- @AllArgsConstructor is Lombox annotation for a constructor with all arguments
 * --- @NoArgsConstructor is Lombox annotation for a constructor with no arguments
 * --- @Entity annotation is used to mark the class as a persistent Java class.
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Convert(attributeName ="person", converter = JsonType.class)
public class Person implements Comparable<Person> {

    /** automatic unique identifier for Person record
     * --- Id annotation is used to specify the identifier property of the entity.
     * ----GeneratedValue annotation is used to specify the primary key generation strategy to use.
     * ----- The strategy is to have the persistence provider pick an appropriate strategy for the particular database.
     * ----- GenerationType.AUTO is the default generation type and it will pick the strategy based on the used database.
     */ 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    /** Many to Many relationship with PersonRole
     * --- @ManyToMany annotation is used to specify a many-to-many relationship between the entities.
     * --- FetchType.EAGER is used to specify that data must be eagerly fetched, meaning that it must be loaded immediately.
     * --- Collection is a root interface in the Java Collection Framework, in this case it is used to store PersonRole objects.
     * --- ArrayList is a resizable array implementation of the List interface, allowing all elements to be accessed using an integer index.
     * --- PersonRole is a POJO, Plain Old Java Object. 
     */
    @ManyToMany(fetch = EAGER)
    private Collection<PersonRole> roles = new ArrayList<>();

    /** email, password, roles are key attributes to login and authentication
     * --- @NotEmpty annotation is used to validate that the annotated field is not null or empty, meaning it has to have a value.
     * --- @Size annotation is used to validate that the annotated field is between the specified boundaries, in this case greater than 5.
     * --- @Email annotation is used to validate that the annotated field is a valid email address.
     * --- @Column annotation is used to specify the mapped column for a persistent property or field, in this case unique and email.
     */
    @NotEmpty
    @Size(min=5)
    @Column(unique=true)
    @Email
    private String email;

    @NotEmpty
    private String password;

    /** name, dob are attributes to describe the person
     * --- @NonNull annotation is used to generate a constructor with AllArgsConstructor Lombox annotation.
     * --- @Size annotation is used to validate that the annotated field is between the specified boundaries, in this case between 2 and 30 characters.
     * --- @DateTimeFormat annotation is used to declare a field as a date, in this case the pattern is specified as yyyy-MM-dd.
     */ 
    @NonNull
    @Size(min = 2, max = 30, message = "Name (2 to 30 chars)")
    private String name;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date dob;

    /** stats is used to store JSON for daily stat$
     * --- @JdbcTypeCode annotation is used to specify the JDBC type code for a column, in this case json.
     * --- @Column annotation is used to specify the mapped column for a persistent property or field, in this case columnDefinition is specified as jsonb.
     * * * Example of JSON data:
        "stats": {
            "2022-11-13": {
                "calories": 2200,
                "steps": 8000
            }
        }
    */
    @JdbcTypeCode(SqlTypes.JSON)
    @Column(columnDefinition = "jsonb")
    private Map<String,Map<String, Object>> stats = new HashMap<>(); 
    

    /** Custom constructor for Person when building a new Person object from an API call
     * @param email, a String
     * @param password, a String
     * @param name, a String
     * @param dob, a Date
     */
    public Person(String email, String password, String name, Date dob, PersonRole role) {
        this.email = email;
        this.password = password;
        this.name = name;
        this.dob = dob;
        this.roles.add(role);
    }

    /** Custom getter to return age from dob attribute
     * @return int, the age of the person
    */
    public int getAge() {
        if (this.dob != null) {
            LocalDate birthDay = this.dob.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            return Period.between(birthDay, LocalDate.now()).getYears(); }
        return -1;
    }

    /** Custom compareTo method to compare Person objects by name
     * @param other, a Person object
     * @return int, the result of the comparison
     */
    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }

    /** 1st telescoping method to create a Person object with USER role
     * @param name
     * @param email
     * @param password
     * @param dob
     * @return Person
     *  */ 
    public static Person createPerson(String name, String email, String password, String dob) {
        // By default, Spring Security expects roles to have a "ROLE_" prefix.
        return createPerson(name, email, password, dob, Arrays.asList("ROLE_USER"));
    }
    /** 2nd telescoping method to create a Person object with parameterized roles
     * @param roles 
     */
    public static Person createPerson(String name, String email, String password, String dob, List<String> roleNames) {
        Person person = new Person();
        person.setName(name);
        person.setEmail(email);
        person.setPassword(password);
        try {
            Date date = new SimpleDateFormat("MM-dd-yyyy").parse(dob);
            person.setDob(date);
        } catch (Exception e) {
            // handle exception
        }
    
        List<PersonRole> roles = new ArrayList<>();
        for (String roleName : roleNames) {
            PersonRole role = new PersonRole(roleName);
            roles.add(role);
        }
        person.setRoles(roles);
    
        return person;
    }
   
    /** Static method to initialize an array list of Person objects
     * uses createPerson method to create Person objects
     * sorts the list of Person objects using Collections.sort which uses the compareTo method 
     * @return Person[], an array of Person objects
     */
    public static Person[] init() {
        List<Person> people = new ArrayList<>();
        people.add(createPerson("Thomas Edison", "toby@gmail.com", "123toby", "01-01-1840", Arrays.asList("ROLE_ADMIN", "ROLE_USER", "ROLE_TESTER")));
        people.add(createPerson("Alexander Graham Bell", "lexb@gmail.com", "123lex", "01-01-1847"));
        people.add(createPerson("Nikola Tesla", "niko@gmail.com", "123niko", "01-01-1850"));
        people.add(createPerson("Madam Currie", "madam@gmail.com", "123madam", "01-01-1860"));
        people.add(createPerson("Grace Hopper", "hop@gmail.com", "123hop", "12-09-1906"));
        people.add(createPerson("John Mortensen", "jm1021@gmail.com", "123Qwerty!", "10-21-1959", Arrays.asList("ROLE_ADMIN")));

        Collections.sort(people);

        return people.toArray(new Person[0]);
    }

    /** Static method to print Person objects from an array
     * @param args, not used
     */
    public static void main(String[] args) {
        // obtain Person from initializer
        Person persons[] = init();

        // iterate using "enhanced for loop"
        for( Person person : persons) {
            System.out.println(person);  // print object
        }
    }

}

Person Generated Code

This code is generated as a result of Lombok annotations. Lombok simplifies the creation of common methods like getters, setters, hashCode, toString, and equals.

Getters and Setters

Description: Lombok generates getter and setter methods for each field in the Person class. These methods allow for accessing and modifying the private fields of the class.

@Generated
   public Long getId() {
      return this.id;
   }

   @Generated
   public Collection<PersonRole> getRoles() {
      return this.roles;
   }

   @Generated
   public String getEmail() {
      return this.email;
   }

   @Generated
   public String getPassword() {
      return this.password;
   }

   @Generated
   public @NonNull String getName() {
      return this.name;
   }

   @Generated
   public Date getDob() {
      return this.dob;
   }

   @Generated
   public Map<String, Map<String, Object>> getStats() {
      return this.stats;
   }

   @Generated
   public void setId(final Long id) {
      this.id = id;
   }

   @Generated
   public void setRoles(final Collection<PersonRole> roles) {
      this.roles = roles;
   }

   @Generated
   public void setEmail(final String email) {
      this.email = email;
   }

   @Generated
   public void setPassword(final String password) {
      this.password = password;
   }

   @Generated
   public void setName(final @NonNull String name) {
      if (name == null) {
         throw new NullPointerException("name is marked non-null but is null");
      } else {
         this.name = name;
      }
   }

   @Generated
   public void setDob(final Date dob) {
      this.dob = dob;
   }

   @Generated
   public void setStats(final Map<String, Map<String, Object>> stats) {
      this.stats = stats;
   }

Equals Method

Description: Lombok generates an equals method to compare two Person objects. This method checks if the objects are equal by comparing their fields.


   @Generated
   public boolean equals(final Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof Person)) {
         return false;
      } else {
         Person other = (Person)o;
         if (!other.canEqual(this)) {
            return false;
         } else {
            label95: {
               Object this$id = this.getId();
               Object other$id = other.getId();
               if (this$id == null) {
                  if (other$id == null) {
                     break label95;
                  }
               } else if (this$id.equals(other$id)) {
                  break label95;
               }

               return false;
            }

            Object this$roles = this.getRoles();
            Object other$roles = other.getRoles();
            if (this$roles == null) {
               if (other$roles != null) {
                  return false;
               }
            } else if (!this$roles.equals(other$roles)) {
               return false;
            }

            Object this$email = this.getEmail();
            Object other$email = other.getEmail();
            if (this$email == null) {
               if (other$email != null) {
                  return false;
               }
            } else if (!this$email.equals(other$email)) {
               return false;
            }

            label74: {
               Object this$password = this.getPassword();
               Object other$password = other.getPassword();
               if (this$password == null) {
                  if (other$password == null) {
                     break label74;
                  }
               } else if (this$password.equals(other$password)) {
                  break label74;
               }

               return false;
            }

            label67: {
               Object this$name = this.getName();
               Object other$name = other.getName();
               if (this$name == null) {
                  if (other$name == null) {
                     break label67;
                  }
               } else if (this$name.equals(other$name)) {
                  break label67;
               }

               return false;
            }

            Object this$dob = this.getDob();
            Object other$dob = other.getDob();
            if (this$dob == null) {
               if (other$dob != null) {
                  return false;
               }
            } else if (!this$dob.equals(other$dob)) {
               return false;
            }

            Object this$stats = this.getStats();
            Object other$stats = other.getStats();
            if (this$stats == null) {
               if (other$stats != null) {
                  return false;
               }
            } else if (!this$stats.equals(other$stats)) {
               return false;
            }

            return true;
         }
      }
   }

   @Generated
   protected boolean canEqual(final Object other) {
      return other instanceof Person;
   }

HashCode Method

Description: Lombok generates a hashCode method to provide a hash code for the Person object. This method is used in hashing-based collections like HashMap.

Practical Purposes:

Hash-Based Collections: The hashCode method is essential for the performance of hash-based collections like HashMap, HashSet, and Hashtable. It allows these collections to quickly locate objects. Consistency with Equals: The hashCode method must be consistent with the equals method. If two objects are considered equal according to the equals method, they must have the same hash code. Caching: Hash codes can be cached to improve performance, especially for immutable objects.


   @Generated
   public int hashCode() {
      int PRIME = true;
      int result = 1;
      Object $id = this.getId();
      result = result * 59 + ($id == null ? 43 : $id.hashCode());
      Object $roles = this.getRoles();
      result = result * 59 + ($roles == null ? 43 : $roles.hashCode());
      Object $email = this.getEmail();
      result = result * 59 + ($email == null ? 43 : $email.hashCode());
      Object $password = this.getPassword();
      result = result * 59 + ($password == null ? 43 : $password.hashCode());
      Object $name = this.getName();
      result = result * 59 + ($name == null ? 43 : $name.hashCode());
      Object $dob = this.getDob();
      result = result * 59 + ($dob == null ? 43 : $dob.hashCode());
      Object $stats = this.getStats();
      result = result * 59 + ($stats == null ? 43 : $stats.hashCode());
      return result;
   }

ToString Method

Description: Lombok generates a toString method to provide a string representation of the Person object. This method is useful for debugging and logging.

This is a College Board test requirement.


   @Generated
   public String toString() {
      String var10000 = String.valueOf(this.getId());
      return "Person(id=" + var10000 + ", roles=" + String.valueOf(this.getRoles()) + ", email=" + this.getEmail() + ", password=" + this.getPassword() + ", name=" + this.getName() + ", dob=" + String.valueOf(this.getDob()) + ", stats=" + String.valueOf(this.getStats()) + ")";
   }

Constructors

Description: Lombok generates constructors for the Person class. These include a no-argument constructor and a constructor with all fields.


   @Generated
   public Person(final Long id, final Collection<PersonRole> roles, final String email, final String password, final @NonNull String name, final Date dob, final Map<String, Map<String, Object>> stats) {
      if (name == null) {
         throw new NullPointerException("name is marked non-null but is null");
      } else {
         this.id = id;
         this.roles = roles;
         this.email = email;
         this.password = password;
         this.name = name;
         this.dob = dob;
         this.stats = stats;
      }
   }

   @Generated
   public Person() {
   }