Why is the child collection is null in One-To-Many relationship of spring boot application? - spring

I create a spring boot application with MySQL,JPA,Web dependencies,and manually config my database settings in .properties file of Spring boot. I passed compiling, and started application successfully, and adding one record is normal fine.
BUT, i use method 'findAll(Pageable pageable)' i got a problem, that was
Could not write JSON: failed to lazily initialize a collection of roleļ¼Œcould not initialize proxy - no Session
I got confused, i started to debug my code, finally i found that the child collection of the result is null, and it contained an error, which is
"Exception occurred: com.sun.jdi.InvocationException occurred invoking method.."
I tried a lot to fix my code, but no use.
who can help me?
The entity relationship is a simple one to many:
TeacherInfo entity and ClassInfo entity, teacher manage multiple classes, just simple as this.
here is the enter point of my app:
#SpringBootApplication(exclude= {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceAutoConfiguration.class
})
#EnableTransactionManagement
public class OrmTestApplication {
public static void main(String[] args) {
SpringApplication.run(OrmTestApplication.class, args);
}
}
Database properties setting is here:
spring.datasource.primary.url=jdbc:mysql://localhost:3306/ormtest?useSSL=false
spring.datasource.primary.username=root
spring.datasource.primary.password=BlaNok2700
spring.datasource.primary.driver-class-name = com.mysql.jdbc.Driver
hibernate.hbm2ddl.auto = update
hibernate.show-sql = true
My Data base configure java code is here:
Configuration
#EnableJpaRepositories(basePackages = "com.lanjian.ormtest.repositories", entityManagerFactoryRef = "primaryEntityManagerFactory", transactionManagerRef = "primaryTransactionManager")
public class PrimaryDbConfig {
#Autowired
private Environment env;
#Bean
#ConfigurationProperties(prefix="spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource primaryDataSource() {
DataSourceProperties dbProperty = primaryDataSourceProperties();
return DataSourceBuilder.create()
.driverClassName(dbProperty.getDriverClassName())
.url(dbProperty.getUrl())
.username(dbProperty.getUsername())
.password(dbProperty.getPassword())
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(primaryDataSource());
factory.setPackagesToScan("com.lanjian.ormtest.entities");
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.show-sql", env.getProperty("hibernate.show-sql"));
factory.setJpaProperties(jpaProperties);
return factory;
}
#Bean
public PlatformTransactionManager primaryTransactionManager() {
EntityManagerFactory factory = primaryEntityManagerFactory().getObject();
return new JpaTransactionManager(factory);
}
}
My REST controller method is here:
#Autowired
private TeacherRepository teacherRepository;
#GetMapping("/page")
public Page<TeacherInfo> page(Pageable pageable){
Page<TeacherInfo> list = teacherRepository.findAll(pageable);
return list;
}
What happened
After i started my application, and use postman send request, i got this:
got a 500 error
And i debugger my code, found this:
child collection is null
In the picture, 'classes' is a list collection, but it is null, i don't understand.
Here are the TeacherInfo entity I defined
#Entity
#Table(name = "teacher")
public class TeacherInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private byte age;
private boolean male;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY, mappedBy="chargedTeacher")
private List<ClassInfo> classes = new ArrayList<>();
public void initialize() {
for (ClassInfo classInfo : classes) {
classInfo.setChargedTeacher(this);
for (StudentInfo studentInfo : classInfo.getStudents()) {
studentInfo.setClassInfo(classInfo);
}
}
}
//Setters and Getters}
Here is the ClassInfo Entity i defined
#Entity
#Table(name = "class_info")
public class ClassInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int capacity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "teacher_id",nullable=false)
#JsonIgnore
private TeacherInfo chargedTeacher;
#OneToMany(cascade = CascadeType.ALL,fetch=FetchType.LAZY,mappedBy="classInfo")
private List<StudentInfo> students = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public TeacherInfo getChargedTeacher() {
return chargedTeacher;
}
public void setChargedTeacher(TeacherInfo chargedTeacher) {
this.chargedTeacher = chargedTeacher;
}
public List<StudentInfo> getStudents() {
return students;
}
public void setStudents(List<StudentInfo> students) {
this.students = students;
}
}

I think that the problem may come from Transactionality and JPA Fetching types.
Your repository method is being invoked not using a transaction, which implies that the transaction is on the boundaries of the method invocation (which might not be wrong). Spring returns a Page with objects but when it tries to serialize them, transaction is gone so no way to access childs.
I would suggest to put the JPA relationship as EAGER fetching, allowing all the objects to be present on the repository result when the transaction ends.
EDIT:
Answer to comments
#Bean
public PlatformTransactionManager primaryTransactionManager(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}

Related

Using jsonb postgres format into Spring data jdbc

I have the following test table:
CREATE TABLE user (
id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100),
address jsonb
)
What I want to do is loading this table into an entity
public class Buddy{
#Id
private UUID id;
private String name;
private Address address;
//getter and setter
}
where the Address is something like
public class Address {
#JsonProperty("name")
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Is not clear to me how can I convert my json string into a java Pojo?
is possible doing this, or is out of the scope?
With Spring Data JDBC
Sample data
-- You can't create a database table named 'user' in PostgreSQL
drop table users;
CREATE TABLE users (
id VARCHAR(10) NOT NULL PRIMARY KEY,
name VARCHAR(100),
address jsonb
);
insert into users values('1', 'Jhon Doe', '{ "name": "Main St Anytown, USA"}');
Repository
#Repository
public interface BuddyRepository extends CrudRepository<Buddy, String>{
}
You can create and register a Converter
#Configuration
#ComponentScan("com.example.demo")
public class Config extends AbstractJdbcConfiguration {
#Bean
public DataSource pgDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/db");
dataSource.setUsername("postgres");
dataSource.setPassword("postgres");
return dataSource;
}
#Bean
public JdbcCustomConversions jdbcCustomConversions() {
final List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(EntityWritingConverter.INSTANCE);
converters.add(EntityReadingConverter.INSTANCE);
return new JdbcCustomConversions(converters);
}
#Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
#WritingConverter
enum EntityWritingConverter implements Converter<Address, PGobject> {
INSTANCE;
#Override
public PGobject convert(Address source) {
ObjectMapper objectMapper = new ObjectMapper();
PGobject jsonObject = new PGobject();
jsonObject.setType("json");
try {
jsonObject.setValue(objectMapper.writeValueAsString(source));
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return jsonObject;
}
}
#ReadingConverter
enum EntityReadingConverter implements Converter<PGobject, Address> {
INSTANCE;
#Override
public Address convert(PGobject pgObject) {
ObjectMapper objectMapper = new ObjectMapper();
String source = pgObject.getValue();
try {
return objectMapper.readValue(source, Address.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
}
Run
#Autowired
BuddyRepository repository;
#Override
public void run(String... arg0) throws Exception {
Optional<Buddy> pojo = repository.findById("1");
System.out.println(pojo.get().id);
System.out.println(pojo.get().address.getName());
}
Results
1 Main St Anytown, USA
With JPA and Hibernate
Try this way
Maven dependency
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>${hypersistence-utils.version}</version>
</dependency>
Or Gradle dependency
compile group: 'io.hypersistence', name: 'hypersistence-utils-hibernate-55', version: '3.0.1'
--
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType
import org.hibernate.annotations.TypeDef
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Buddy{
#Id
private UUID id;
private String name;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private Address address;
//getter and setter
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Address {
#JsonProperty("name")
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

Spring JPA Transaction ID

I have added an attribute to all my entities - transaction id - which is a sequence generated value that I bump up once in each transaction.
I also store the transaction id with user and start/end times so I have an audit trail for every change in the database.
What is the best way to handle storing a complete graph, where I basically only want to apply the transaction id to those entities that are actually dirty?
I can put a #PrePersist and #PreUpdate on the transaction id column, but how do I retrieve the value for the current transaction id? Is there a way to store and retrieve a value on the transaction object or other JPA controller? Do I need to use a ThreadLocal solution?
Ok, here is what I did. It seems to work in all of the use cases, though I have not done any performance testing, etc. If anyone sees anything that may be non-optimal or may fail in certain situations, please point it out.
Here is the base service class that all #Service implementations must extend:
public class BaseService
{
private final ActivityService activityService;
private final ApplicationEventPublisher applicationEventPublisher;
public static ThreadLocal<Activity> transaction = new ThreadLocal<>();
public BaseService(ActivityService activityService, ApplicationEventPublisher applicationEventPublisher)
{
this.activityService = activityService;
this.applicationEventPublisher = applicationEventPublisher;
}
Object executeWithinActivity(Updater updater)
{
boolean startedLocally = false;
try
{
if (transaction.get() == null)
{
startedLocally = true;
Activity activity = activityService.startTransaction();
transaction.set(activity);
}
return updater.execute(transaction.get());
}
finally
{
if (startedLocally)
{
applicationEventPublisher.publishEvent(new TransactionEvent());
Activity activity = transaction.get();
activityService.endTransaction(activity);
}
}
}
protected interface Updater
{
Object execute (Activity activity);
}
static class TransactionEvent
{
}
}
Activity is the entity that represents the stored transaction id:
#Entity
#Getter #Setter
#Table(name = "transactions", schema = "public", catalog = "euamdb")
public class Activity
{
#Id
#Column(name = "transaction_id", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tx_generator")
#SequenceGenerator(name = "tx_generator", sequenceName = "transaction_seq", allocationSize = 1)
private long transactionId;
#Basic
#Column(name = "user_id", length = 24)
private String userId;
#Basic
#Column(name = "transaction_start")
#CreationTimestamp
private Date transactionStart;
#Basic
#Column(name = "transaction_end")
#UpdateTimestamp
private Date transactionEnd;
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (!(o instanceof Activity)) return false;
Activity that = (Activity) o;
return transactionId == that.transactionId;
}
#Override
public int hashCode()
{
return Long.hashCode(transactionId);
}
}
ActivityService (which does not extend BaseService):
#Service
public class ActivityService
{
private final ActivityRepository activityRepository;
private final AuthUserService authService;
#Autowired
public ActivityService(ActivityRepository activityRepository, AuthUserService authService)
{
this.activityRepository = activityRepository;
this.authService = authService;
}
#Transactional
public Activity startTransaction()
{
Activity activity = new Activity();
activity.setTransactionStart(new Date());
activity.setUserId(authService.getAuthenticatedUserId());
activityRepository.save(activity);
return activity;
}
#Transactional
public void endTransaction(Activity activity)
{
activity.setTransactionEnd(new Date());
activityRepository.save(activity);
}
}
The base entity class for all entities (excepting Activity):
#MappedSuperclass
#Getter #Setter
public class BaseEntity
{
#Basic
#Column(name = "transaction_id")
private Long transactionId;
#PrePersist
#PreUpdate
public void setupTransaction ()
{
ThreadLocal<Activity> transaction = BaseService.transaction;
Activity activity = transaction.get();
long transactionId = activity.getTransactionId();
setTransactionId(transactionId);
}
}
An example of a service:
#Service
public class OrganizationService extends BaseService
{
private final OrgUserRepository orgUserRepository;
private final UserService userService;
#Autowired
public OrganizationService(ActivityService activityService,
OrgUserRepository orgUserRepository,
UserService userService,
ApplicationEventPublisher applicationEventPublisher)
{
super(activityService, applicationEventPublisher);
this.orgUserRepository = orgUserRepository;
this.userService = userService;
}
#Transactional
public OrgUser save(User user, OrgUser orgUser)
{
return (OrgUser) executeWithinActivity(activity ->
{
orgUser.setUser(userService.save(user));
return orgUserRepository.save(orgUser);
});
}
}
UserService also will extend BaseService and the save(OrgUser) method will also executeWithinActivity.
Finally, the commit listener:
#Component
public class AfterCommitListener
{
#TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void doAfterTxComplete(BaseService.TransactionEvent event)
{
BaseService.transaction.remove();
}
}

Unable to insert rows in database using Spring Batch + Spring data

I am working with legacy database in which db tables has no keys. For java Sake i have to used Id annotation. My goal is to read data from .dat file and insert it into table. I'm using spring batch for the above said purpose. To improve the performance threading is used. But i'm getting insertion/updation issue that i'm unable to figure out. I have referenced many sources but none seems to solve my purpose. Kindly help me out by giving some appropriate solution or reference. Thanks in advance...
Entity.java
#Entity
#Table(name = "int_repl_mkt_val")
public class IntReplMktVal implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private IntReplMktValId id;
#Column(name = "acct_sys_cd")
private String acctSysCd;
#Column(name = "co_num", nullable = false)
private Integer coNum;
#Column(name = "last_mod_tmstmp")
#Temporal(TemporalType.TIMESTAMP)
private Date lastModTmstmp;
#Column(name = "pim_owned", nullable = false)
private String pimOwned;
#Column(name = "position", nullable = false)
private BigDecimal position;
#Column(name = "pricing_plan")
private String pricingPlan;
#Column(name="source_system",nullable=false)
private String sourceSystem;
... getter and setter
}
EmbeddedClass.java
#Embeddable
public class IntReplMktValId implements Serializable
{
private static final long serialVersionUID = 4824041485763129937L;
#Column(name = "acct_id",nullable=false)
private Integer acctId;
#Column(name = "asset_id",nullable=false)
private Integer assetId;
... getter and setter
}
jpaRepository.class
#Repository
public interface IntReplMktValRepository extends JpaRepository<IntReplMktVal, IntReplMktValId>
{
}
BatchConfiguration.class
#Configuration
public class IMAPPositionBatchConfiguration
{
#Autowired
JobBuilderFactory jobBuilderFactory;
#Autowired
StepBuilderFactory stepBuilderFactory;
#StepScope
#Bean(name="imapPositionReader")
public FlatFileItemReader<IMAPPositionInputMapperDTO> reader(#Value("#{jobParameters['fileName']}") String fileName) throws IOException
{
FlatFileItemReader<IMAPPositionInputMapperDTO> newBean = new FlatFileItemReader<>();
newBean.setName("fileReader");
newBean.setResource(new InputStreamResource(FileUtils.openInputStream(new File(fileName))));
newBean.setLineMapper(this.lineMapper());
newBean.setLinesToSkip(1);
return newBean;
}
public DefaultLineMapper<IMAPPositionInputMapperDTO> lineMapper()
{
DefaultLineMapper<IMAPPositionInputMapperDTO> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(this.lineTokenizer());
IMAPPositionReader imapPositionReader = new IMAPPositionReader();
lineMapper.setFieldSetMapper(imapPositionReader);
return lineMapper;
}
public DelimitedLineTokenizer lineTokenizer()
{
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter("|");
tokenizer.setNames("field1","field2","field3");
tokenizer.setIncludedFields(5,4,7);
return tokenizer;
}
public ItemProcessor<IMAPPositionInputMapperDTO, IntReplMktVal> processor()
{
return new IMAPPositionProcessor();
}
#Bean(name="imapPositionBatchWriter")
public ItemWriter<IntReplMktVal> writer()
{
return new IMAPPositionWriter();
}
#Bean(name="imapPositionListener")
public JobExecutionListenerSupport jobCompletionListener()
{
return new IMAPPositionJobListener();
}
#Bean(name="imapPositionTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50);
executor.setMaxPoolSize(100);
return executor;
}
#Bean(name="imapPositionStep")
public Step step(#Autowired #Qualifier("imapPositionTaskExecutor")TaskExecutor taskExecutor) throws IOException
{
return stepBuilderFactory.get("imapPositionStep")
.<IMAPPositionInputMapperDTO, IntReplMktVal>chunk(100)
.reader(this.reader(null))
.processor(this.processor())
.writer(this.writer())
.taskExecutor(taskExecutor)
.build();
}
#Bean(name="imapPositionFileImportJob")
public Job importUserJob(#Autowired #Qualifier("imapPositionStep") Step step)
{
return jobBuilderFactory
.get("imapPositionFileImportJob"+new Date())
.incrementer(new RunIdIncrementer())
.listener(this.jobCompletionListener())
.flow(step)
.end()
.build();
}
}
BatchWriter.java
public class IMAPPositionWriter implements ItemWriter<IntReplMktVal>
{
#Autowired
IntReplMktValRepository intReplMktValRepository;
#Override
public void write(List<? extends IntReplMktVal> items) throws Exception
{
intReplMktValRepository.saveAll(items);
}
}
ErrorLog
2019-06-07 17:22:01,522 ERROR [scopedTarget.imapPositionTaskExecutor-4] org.hibernate.internal.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [org.hibernate.HibernateException: Duplicate identifier in table for: [com.capgroup.horizon.pricecapture.entities.IntReplMktVal#component[acctId,assetId]{assetId=274800, acctId=1}]]
NOTE: I have to insert all the data into table regardless of duplication or any other issue as keys are not defined so every data is valid.
Actually the problem was due to duplication found in persistent context which was resolved by setting the chunk size to 1.

springboot exceptionhandling without controller class

How to handle exception handling in Spring Boot 1.5.4 without controller class? Currently, I have only entity & repository class as below.
Task.class: (entity)
#Entity
#Table(name = "task")
public class Task implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Length(min = 1)
private String name;
public Task() {
}
public Task(String name) {
this.name = name;
}
public Task(Long id, String name) {
this.name = name;
}
public long getId() {
return id;
}
public String getName(){
return name;
}
}
Repository.class:
public interface TaskRepository extends PagingAndSortingRepository<Task, Long> {
}
POST method: return 200 ok
http://localhost:8080/tasks
{
"name" : "test"
}
But,
{
"name" : ""
}
returns 500 , instead of 400 error.
Pls let me know, if any way to handle this exception without a controller class.
You could use a global #ExceptionHandler with the #ControllerAdvice annotation. Basically, you define which Exception to handle with #ExceptionHandler within the class with #ControllerAdvice annotation, and then you implement what you want to do when that exception is thrown.
Like this:
#ControllerAdvice(basePackageClasses = RepositoryRestExceptionHandler.class)
public class GlobalExceptionHandler {
#ExceptionHandler({ValidationException.class, JsonParseException.class})
public ResponseEntity<Map<String, String>> yourExceptionHandler(Exception e) {
Map<String, String> response = new HashMap<String, String>();
response.put("message", "Bad Request");
return new ResponseEntity<Map<String, String>>(response, HttpStatus.BAD_REQUEST);
}
}
See also: http://www.ekiras.com/2016/02/how-to-do-exception-handling-in-springboot-rest-application.html

LazyInitializationException Spring and Hibernate

I am getting this exception nested exception is org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.ibm.ro.model.Location.subLocations, could not initialize proxy - no Session.
I do get that upon accessing the collection, the transaction has already been closed that's why the code is throwing this exception. Here is my sample code
#Entity
#Table(name="location")
public class Location extends BaseEntity {
private static final long serialVersionUID = 1L;
private String name;
private List<Location> subLocations;
private Location location;
#Column(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "location")
public List<Location> getSubLocations() {
return subLocations;
}
public void setSubLocations(List<Location> subLocations) {
this.subLocations = subLocations;
}
#ManyToOne(fetch = FetchType.LAZY)
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
#Override
public String toString() {
return "Location [name=" + name + ", subLocations=" + subLocations
+ "]";
}
}
Here is my DAO:
#Repository("locationDao")
public class LocationDao implements ILocationDao{
#Autowired
private SessionFactory sessionFactory;
#Override
public List<Location> getAll() {
Session sess = getSession();
return sess.createCriteria(Location.class).setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY).list();
}
}
Then here is my service :
#Service("locationService")
#Transactional
public class LocationService implements ILocationService{
#Autowired
ILocationDao dao;
#Override
public List<Location> getAll() {
return dao.getAll();
}
}
Here is the controller where the exception is being thrown:
#Controller
public class BaseController {
#Autowired
ILocationService service;
private static final String VIEW_INDEX = "index";
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(BaseController.class);
#RequestMapping(value = "/", method = RequestMethod.GET)
public String location(ModelMap model) {
logger.debug(service.getAll().toString());
return VIEW_INDEX;
}
}
What can I do to fix the problem without using OpenSessionInViewFilter?
You can iterate your Location inside your service (where you still have your transaction) and call Hibernate.initialize on the elements, the force initialization of a persistent collection.
#Override
public List<Location> getAll() {
List<Location> locations = dao.getAll();
for (Location location : locations ) {
Hibernate.intialize(location.getSubLocations())
}
return locations;
}

Resources