똘이의 개발 Life

JPA org.hibernate.mapping.Value.getSelectables() value is null 본문

Back-End/JPA

JPA org.hibernate.mapping.Value.getSelectables() value is null

또리또리똘 2024. 1. 23. 16:15
728x90

프로젝트 스펙 

Springboot 3.x , java 17 , spring jpa , spring web , spring validation , spring starter

As-Is

Entity 개발 후 프로젝트 구동 시 에러 발생

아래 에러 로그 확인 

2024-01-23T14:19:38.064+09:00  INFO 26464 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-01-23T14:19:38.159+09:00  INFO 26464 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.4.1.Final
2024-01-23T14:19:38.207+09:00  INFO 26464 --- [  restartedMain] o.h.c.internal.RegionFactoryInitiator    : HHH000026: Second-level cache disabled
2024-01-23T14:19:38.519+09:00  INFO 26464 --- [  restartedMain] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-01-23T14:19:38.936+09:00  WARN 26464 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Cannot invoke "org.hibernate.mapping.Value.getSelectables()" because "value" is null
2024-01-23T14:19:38.939+09:00  INFO 26464 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-01-23T14:19:38.954+09:00  INFO 26464 --- [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2024-01-23T14:19:38.958+09:00  INFO 26464 --- [  restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2024-01-23T14:19:38.971+09:00  INFO 26464 --- [  restartedMain] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-01-23T14:19:38.994+09:00 ERROR 26464 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Cannot invoke "org.hibernate.mapping.Value.getSelectables()" because "value" is null
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1773) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1231) ~[spring-context-6.1.3.jar:6.1.3]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:949) ~[spring-context-6.1.3.jar:6.1.3]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624) ~[spring-context-6.1.3.jar:6.1.3]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.2.2.jar:3.2.2]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.2.jar:3.2.2]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.2.jar:3.2.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:334) ~[spring-boot-3.2.2.jar:3.2.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.2.jar:3.2.2]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.2.jar:3.2.2]
	at com.evzone.proxyapi.ProxyApiApplication.main(ProxyApiApplication.java:11) ~[main/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.2.2.jar:3.2.2]
Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.mapping.Value.getSelectables()" because "value" is null
	at org.hibernate.mapping.Constraint.addColumns(Constraint.java:117) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.mapping.PersistentClass.createPrimaryKey(PersistentClass.java:427) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.boot.model.internal.CreateKeySecondPass.doSecondPass(CreateKeySecondPass.java:33) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1815) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1770) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:332) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1432) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1503) ~[hibernate-core-6.4.1.Final.jar:6.4.1.Final]
	at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75) ~[spring-orm-6.1.3.jar:6.1.3]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376) ~[spring-orm-6.1.3.jar:6.1.3]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-6.1.3.jar:6.1.3]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-6.1.3.jar:6.1.3]
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:352) ~[spring-orm-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1820) ~[spring-beans-6.1.3.jar:6.1.3]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1769) ~[spring-beans-6.1.3.jar:6.1.3]
	... 21 common frames omitted


Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.mapping.Value.getSelectables()" because "value" is null

 

에러 요약

Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.mapping.Value.getSelectables()" because "value" is null

 

hibernate 쪽에 value 값 하나가 null 인 것 같은데 

다른 프로젝트는 잘 돌아간다.

무엇이 문제일까...

Challenge

나는 뛰어난 개발자는 아니지만 3년 7개월 짜리 눈치 밥이 있다. 

 

at org.hibernate.mapping.Constraint.addColumns(Constraint.java:117) ~

public void addColumns(Value value) {
		for ( Selectable selectable : value.getSelectables() ) {
			if ( selectable.isFormula() ) {
				throw new MappingException( "constraint involves a formula: " + name );
			}
			else {
				addColumn( (Column) selectable );
			}
		}
	}


at org.hibernate.mapping.PersistentClass.createPrimaryKey(PersistentClass.java:427) ~

public void createPrimaryKey() {
		//Primary key constraint
		final Table table = getTable();
		final PrimaryKey pk = new PrimaryKey( table );
		pk.setName( PK_ALIAS.toAliasString( table.getName() ) );
		pk.addColumns( getKey() );

		table.setPrimaryKey( pk );
	}

 

대충 에러 로그를 보니 Colums , PK 뭐 이런 쪽에서 에러가 나오는 것 같다. 

Entity쪽에서 문제가 일어난 것 같은데...

Entity를 다 만들고 수정하자는 나의 생각은 잘못되었다.. 

 

여러분 JPA Entity 개발 시 점진적으로 DB 생성되는거 보면서 개발하세요...

 

찾고 찾던 와중 눈에 들어오는 코드... 

// 이거 아닐 듯?
    @OneToOne( fetch = LAZY  , cascade = CascadeType.ALL )
    @JoinColumn( name = "charger_idx" )
    private ChargerStatus chargerStatus;

    @OneToOne( fetch = LAZY  , cascade = CascadeType.ALL )
    @JoinColumn( name = "charger_idx" )
    private ChargerMode chargerMode;

    @OneToOne( fetch = LAZY  , cascade = CascadeType.ALL )
    @JoinColumn( name = "charger_idx" )
    private ChargerConn chargerConn;

 

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChargerConn {

    @Id
    @OneToOne( mappedBy = "charger" , fetch = FetchType.LAZY )
    @JoinColumn( name = "charger_idx" )
    private Charger charger;

    //---- 생략 -----//

}

 

찾았다 범인 

 

위 코드가 잘 못된 것 같았다.

 

내가 원하는 구조가 따로 있었다... 

ChargerStatus , ChargerConn , ChargerMode 의 PK 자체를 charger_idx로 사용하는 것이다. 

예를 들면 

 

charger_idx name 위치 생성일시 charger_status charger_mode charger_conn  
1 A 강남 2024-01-23 READY STOP ONLINE  
2 B 강서 2024-01-21 WORKING RUN OFFLINE  

 

이런식으로 되어 있는 것을 정규화 해서 

charger_idx name 위치 생성일시
1 A 강남 2024-01-23
2 B 강서 2024-01-21

ChargerStatus

charger_idx charger_status
1 READY
2 WORKING

 

ChargerMode

charger_idx charger_mode
1 STOP
2 RUN

ChargerConn

charger_idx charger_conn
1 On
2 OFF

 

이런식으로 각 테이블 마다 charger_idx 를 키 값으로 사용하고 싶었다. 

 

 

To-Be

첫번째 해결 방식 

 

package com.evzone.proxyapi.entity.charger;

import com.evzone.proxyapi.entity.ProxyServer;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ChargerConn {



    @Id
    @GeneratedValue
    @Column(name = "charger_conn_idx")
    private Long id;

    @OneToOne( mappedBy = "chargerConn" , fetch = FetchType.LAZY )
    private Charger charger;

   	//---  생략 ---- //


}

 

이런 식으로 PK를 하나 생성해줬다. 

그런데 정말 저기서 따로 PK 값을 갖을 필요가 없는데... 완전 리소스 낭비

 

키값이 Charger 객체의 charger_idx 가 되게 해야 한다. 

 

두번 째 해결 

 

은 내가 혼자 삽질하면서 Entity 맵핑쪽을 좀 파봐야겠다. 

 

 

마무리

JPA 핵심은 도메인 모델 설계 ~ Entity Mapping 이 제일 핵심 같다. 

뭔가 설계부터 착착 제대로 조립해야 나중에 강력한 JPA - QueryDSL 의 맛? 을 볼 수 있을 것 같은... 

 

사실 첫 술에 배부를 순 없다. 

나름 강의 보고 실무에 적용해보려 했으나 해당되지 않는 것도 좀 있는 것 같아서 

혼자 삽질을 좀 해봐야 겠다.  

 

퇴근해야지~

728x90