'JPA/Hibernate @GenericGenerator throws StackOverFlow on persisting entity

I have implemented two entities (Department and Employee) with custom ID generator for Employee, and has a unidirectional one-to-many relationship as shown below:-

Department Class

@Setter
@Getter
@NoArgsConstructor
@Entity(name = "department")
@Table(name = "department")
public class Department {

    @Id
    @Column(columnDefinition = "char(36)")
    @GeneratedValue(generator = "dept-generator")
    @GenericGenerator(name = "dept-generator", strategy = "org.hibernate.id.UUIDGenerator")
    private String id;

    @Basic
    @Column(length = 50, nullable = false)
    private String name;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
            name = "dept_emp",
            joinColumns = @JoinColumn(name = "dept_id", referencedColumnName = "id", nullable = false),
            inverseJoinColumns = @JoinColumn(name = "emp_id", referencedColumnName = "id", nullable = false),
            foreignKey = @ForeignKey(ConstraintMode.CONSTRAINT),
            inverseForeignKey = @ForeignKey(ConstraintMode.CONSTRAINT)
    )
    private Set<Employee> employees;

    public Department(String name, Set<Employee> employees) {
        this.name = name;
        this.employees = employees;
    }
}

Employee Class

@Setter
@Getter
@NoArgsConstructor
@Entity(name = "employee")
@Table(name = "employee")
public class Employee {

    @Id
    @Column(columnDefinition = "char(13)")
    @GeneratedValue(generator = "emp-generator")
    @GenericGenerator(name = "emp-generator", strategy = "com.query.example.EmployeeIdGenerator")
    private String id;

    @Basic
    @Column(length = 50, nullable = false)
    private String name;

    @Basic
    @Column(nullable = false)
    private double salary;

    @Basic
    @Column(length = 50, nullable = false)
    private String qualification;

    public Employee(String name, double salary, String qualification) {
        this.name = name;
        this.salary = salary;
        this.qualification = qualification;
    }
}

EmployeeIdGenerator Class

public class EmployeeIdGenerator implements IdentifierGenerator {

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");

    private static final String FORMAT = "E%s%05d";

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object obj) throws HibernateException {
        SessionImpl session1 = (SessionImpl) session;
        CriteriaBuilder builder = session1.getCriteriaBuilder();

        EmployeeSequence sequence = new EmployeeSequence();

        try {
            CriteriaQuery<EmployeeSequence> criteria = builder.createQuery(EmployeeSequence.class);
            Root<EmployeeSequence> root = criteria.from(EmployeeSequence.class);
            criteria.select(root).where(
                    builder.equal(root.get("id"), 1),
                    builder.equal(root.get("date"), sequence.getDate())
            );
            sequence = session1.createQuery(criteria).getSingleResult();
        } catch (Exception ex) {
            try {
                CriteriaQuery<EmployeeSequence> criteria = builder.createQuery(EmployeeSequence.class);
                Root<EmployeeSequence> root = criteria.from(EmployeeSequence.class);
                criteria.select(root).where(builder.equal(root.get("id"), 1));
                sequence = session1.createQuery(criteria).getSingleResult();
                sequence.init();
            } catch (Exception ex1) {
            }
        }

        int counter = sequence.increment();
        session1.merge(sequence);
        return String.format(FORMAT, sequence.getDate().format(DATE_FORMATTER), counter);
    }
}

EmployeeSequence Class

@Setter
@Getter
@NoArgsConstructor
@Entity(name = "employee_sequence")
@Table(name = "employee_sequence")
public class EmployeeSequence {

    @Id
    private int id = 1;

    @Basic
    private LocalDate date = YearMonth.now().atDay(1);

    @Basic
    @Column(length = 5, nullable = false, unique = true)
    private int counter = 1;

    public void init() {
        this.date = YearMonth.now().atDay(1);
        this.counter = 1;
    }

    /**
     * This method will return the pre increment value of counter
     */
    public int increment() {
        return counter++;
    }

    @Override
    public int hashCode() {
        return UUID.randomUUID().hashCode();
    }
}


And here is an example for persisting a Department has a set of employees:-

Set<Employee> employees = new HashSet<>(Arrays.asList(
        new Employee("EMP1", 123, "PhD"),
        new Employee("EMP2", 321, "MA"),
        new Employee("EMP3", 101, "PhD")
));

Department csDept = new Department("Computer Science", employees);

entityManager.getTransaction().begin();
entityManager.persist(csDept);
entityManager.getTransaction().commit();

After running the above example i got StackOverFlow error on EmployeeIdGenerator.generate(EmployeeIdGenerator.java:34) as shown below:-

Exception in thread "main" java.lang.StackOverflowError
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:675)
    at java.lang.StringBuilder.append(StringBuilder.java:208)
    at org.hibernate.query.criteria.internal.compile.CriteriaCompiler$1.generateParameterName(CriteriaCompiler.java:77)
    at org.hibernate.query.criteria.internal.compile.CriteriaCompiler$1.registerLiteralParameterBinding(CriteriaCompiler.java:124)
    at org.hibernate.query.criteria.internal.expression.LiteralExpression.bindLiteral(LiteralExpression.java:131)
    at org.hibernate.query.criteria.internal.expression.LiteralExpression.normalRender(LiteralExpression.java:87)
    at org.hibernate.query.criteria.internal.expression.LiteralExpression.render(LiteralExpression.java:74)
    at org.hibernate.query.criteria.internal.predicate.ComparisonPredicate.render(ComparisonPredicate.java:172)
    at org.hibernate.query.criteria.internal.predicate.AbstractSimplePredicate.render(AbstractSimplePredicate.java:48)
    at org.hibernate.query.criteria.internal.predicate.CompoundPredicate.render(CompoundPredicate.java:162)
    at org.hibernate.query.criteria.internal.predicate.CompoundPredicate.render(CompoundPredicate.java:115)
    at org.hibernate.query.criteria.internal.predicate.CompoundPredicate.render(CompoundPredicate.java:105)
    at org.hibernate.query.criteria.internal.QueryStructure.renderWhereClause(QueryStructure.java:372)
    at org.hibernate.query.criteria.internal.QueryStructure.render(QueryStructure.java:238)
    at org.hibernate.query.criteria.internal.CriteriaQueryImpl.interpret(CriteriaQueryImpl.java:297)
    at org.hibernate.query.criteria.internal.compile.CriteriaCompiler.compile(CriteriaCompiler.java:165)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:751)
    at com.query.example.EmployeeIdGenerator.generate(EmployeeIdGenerator.java:34)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:115)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:185)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:128)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:104)
    at org.hibernate.internal.SessionImpl.persistOnFlush(SessionImpl.java:765)
    at org.hibernate.engine.spi.CascadingActions$8.cascade(CascadingActions.java:341)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:492)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:416)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:525)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:456)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:419)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:151)
    at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:159)
    at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:149)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:82)
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:50)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93)
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1327)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1407)
    at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1625)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1593)
    at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1641)
    at org.hibernate.query.criteria.internal.compile.CriteriaQueryTypeQueryAdapter.getSingleResult(CriteriaQueryTypeQueryAdapter.java:111)


What is the reason that makes EmployeeIdGenerator throws this error when is use persist()?



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source