英文:
"Unable to locate persister" Error with Spring batch + Hibernate ORM
问题
I'm trying to create a system with Spring batch using Hibernate ORM but can't solve this Error
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:652) ~[spring-beans-6.0.2.jar:6.0.2]
...
These are my setting files.
Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
...
hibernate-annotation.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
...
PersonTest1.java
package com.example.batchprocessing.model;
...
HibernateUtil.java
package com.example.batchprocessing.util;
...
BatchConfiguration.java
package com.example.batchprocessing;
...
This is how my project looks like
英文:
I'm trying to create a system with Spring batch using Hibernate ORM but can't solve this Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'reader' defined in class path resource [com/example/batchprocessing/BatchConfiguration.class]: Failed to instantiate [org.springframework.batch.item.file.FlatFileItemReader]: Factory method 'reader' threw exception with message: Unable to locate persister: com.example.batchprocessing.model.PersonTest1
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:652) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:488) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1324) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1161) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.2.jar:6.0.2]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:915) ~[spring-context-6.0.2.jar:6.0.2]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.2.jar:6.0.2]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-3.0.0.jar:3.0.0]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:432) ~[spring-boot-3.0.0.jar:3.0.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) ~[spring-boot-3.0.0.jar:3.0.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[spring-boot-3.0.0.jar:3.0.0]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[spring-boot-3.0.0.jar:3.0.0]
	at com.example.batchprocessing.BatchProcessingApplication.main(BatchProcessingApplication.java:12) ~[classes/:na]
This is how my project looks like
These are my setting files.
Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.0.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>batch-processing-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>batch-processing-complete</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-batch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.hsqldb</groupId>
			<artifactId>hsqldb</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.batch</groupId>
			<artifactId>spring-batch-test</artifactId>
			<scope>test</scope>
		</dependency>
		
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>6.2.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
hibernate-annotation.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<!-- Database connection properties - Driver, URL, user, password -->
		<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
		<property name="hibernate.connection.url">jdbc:postgresql://localhost:5432/testdb</property>
		<property name="hibernate.connection.username">postgres</property>
		<property name="hibernate.connection.password">password</property>
		
		<!-- org.hibernate.HibernateException: No CurrentSessionContext configured! -->
		<property name="hibernate.current_session_context_class">thread</property>
		
		<!-- Mapping with model class containing annotations -->
		<mapping class="com.example.batchprocessing.model.PersonTest1" />
	</session-factory>
</hibernate-configuration>
PersonTest1.java
Notthing special here.
package com.example.batchprocessing.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name="people")
public class PersonTest1 {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="person_id", nullable=false, unique=true, length=11)
	private int personId;
	
	@Column(name="last_name")
	private String lastName;
	
	@Column(name="first_name")
	private String firstName;
	public int getPersonId() {
		return personId;
	}
	public void setPersonId(int personId) {
		this.personId = personId;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
}
HibernateUtil.java
I just copied this from internet so hope there is no problem here.
package com.example.batchprocessing.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.example.batchprocessing.JobCompletionNotificationListener;
public class HibernateUtil {
	private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);
	
	//Annotation based configuration
	private static SessionFactory sessionAnnotationFactory;
	
	private static SessionFactory buildSessionAnnotationFactory() {
		try {
			Configuration configuration = new Configuration();
			configuration.configure("hibernate-annotation.cfg.xml");
			log.info("--- Hibernate Configuration loaded ---");
			
			ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
			log.info("--- Hibernate Annotation serviceRegistry created ---");
			
			SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
			
			return sessionFactory;
			
		} catch (Throwable ex) {
			log.error("--- Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}
	
	public static SessionFactory getSessionAnnotationFactory() {
		sessionAnnotationFactory = buildSessionAnnotationFactory();
		
		return sessionAnnotationFactory;
	}
}
BatchConfiguration.java
reader() is where I am trying to write to DB.
package com.example.batchprocessing;
import javax.sql.DataSource;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.transaction.PlatformTransactionManager;
import com.example.batchprocessing.model.PersonTest1;
import com.example.batchprocessing.util.HibernateUtil;
@Configuration
public class BatchConfiguration {
	
	private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class);
	// tag::readerwriterprocessor[]
	@Bean
	public FlatFileItemReader<Person> reader() {
		
		PersonTest1 personTest = new PersonTest1();
		personTest.setLastName("John");
		personTest.setFirstName("Wick");
		
		// Get Session
		log.info("--- Creating session ---");
		SessionFactory sessionFactory = HibernateUtil.getSessionAnnotationFactory();
		Session session = sessionFactory.getCurrentSession();
		
		log.info("--- Starting transaction ---");
		session.beginTransaction();
		
		log.info("--- Saving model ---");
		session.persist(personTest);
		
		log.info("--- Commiting transaction ---");
		session.getTransaction().commit();
		
		log.info("--- Closing session ---");
		sessionFactory.close();
		
		return new FlatFileItemReaderBuilder<Person>()
			.name("personItemReader")
			.resource(new ClassPathResource("sample-data.csv"))
			.delimited()
			.names(new String[]{"firstName", "lastName"})
			.fieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {{
				setTargetType(Person.class);
			}})
			.build();
	}
	@Bean
	public PersonItemProcessor processor() {
		return new PersonItemProcessor();
	}
	@Bean
	public JdbcBatchItemWriter<Person> writer(DataSource dataSource) {
		return new JdbcBatchItemWriterBuilder<Person>()
			.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
			.sql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)")
			.dataSource(dataSource)
			.build();
	}
	// end::readerwriterprocessor[]
	// tag::jobstep[]
	@Bean
	public Job importUserJob(JobRepository jobRepository,
			JobCompletionNotificationListener listener, Step step1) {
		return new JobBuilder("importUserJob", jobRepository)
			.incrementer(new RunIdIncrementer())
			.listener(listener)
			.flow(step1)
			.end()
			.build();
	}
	@Bean
	public Step step1(JobRepository jobRepository,
			PlatformTransactionManager transactionManager, JdbcBatchItemWriter<Person> writer) {
		return new StepBuilder("step1", jobRepository)
			.<Person, Person> chunk(10, transactionManager)
			.reader(reader())
			.processor(processor())
			.writer(writer)
			.build();
	}
	// end::jobstep[]
}
答案1
得分: 1
This line in HibernateUtil is problematic because applySettings will only apply properties but no mappings:
HibernateUtil中的这行代码存在问题,因为`applySettings`只会应用属性而不会应用映射:
```java
Judging from the implementation of Configuration.configure(String resource), you need to call StandardServiceRegistryBuilder.configure(String resource) to apply the mappings declared in hibernate-annotation.cfg.xml:
根据Configuration.configure(String resource)的实现,你需要调用StandardServiceRegistryBuilder.configure(String resource)来应用在hibernate-annotation.cfg.xml中声明的映射:
Or, if you don't actually have a reason to create the ServiceRegistry yourself, instead call configuration.buildSessionFactory() and let the Configuration class use the internal StandardServiceRegistryBuilder.
或者,如果你实际上没有理由自己创建ServiceRegistry,可以调用configuration.buildSessionFactory(),让Configuration类使用内部的StandardServiceRegistryBuilder。
英文:
This line in HibernateUtil is problematic because applySettings will only apply properties but no mappings:
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        .applySettings(configuration.getProperties())
        .build();
Judging from the implementation of Configuration.configure(String resource), you need to call StandardServiceRegistryBuilder.configure(String resource) to apply the mappings declared in hibernate-annotation.cfg.xml:
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
        .configure("hibernate-annotation.cfg.xml")
        .build();
Or, if you don't actually have a reason to create the ServiceRegistry yourself, instead call configuration.buildSessionFactory() and let the Configuration class use the internal StandardServiceRegistryBuilder.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论