Why do I see different behavior on this Spring repository when it is imported as a dependency into another project? - spring

I am seeing different behavior from a Spring annotated repository when I run my project independently vs. when I add the project as a maven dependency. The delete method on the Spring CrudRepository seems to be where I am seeing the strange behavior.
Here are what I believe are the pertinent pieces of my interface and related entities:
#Repository
public interface JpaProfileRepo extends ProfileRepo, JpaRepository<UserProfileEntity, Long>, GsonProvider {
#Transactional
default void removeProfile(Profile profile){
if(profile == null){
throw new MissingKeyDataException("Missing valid ProfileModel object during removeProfile");
}
UserProfileEntity entity = findEntityByKeys(profile);
if (entity == null) throw new DoesNotExistException();
try{
this.delete(entity);
} catch (EmptyResultDataAccessException ex) {
throw new DoesNotExistException();
}
}
}
public interface ProfileRepo {
void removeProfile(Profile profile);
}
#Entity
#Table(name="user_profiles")
public class UserProfileEntity {
#Id
#SequenceGenerator(name = "user_profiles_id_seq", sequenceName = "user_profiles_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_profiles_id_seq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserEntity user;
#NotNull
#Column(name = "permissions", columnDefinition="TEXT")
private String permissions;
}
#Entity
#Table(name="users", uniqueConstraints ={
#UniqueConstraint(columnNames={"email_address"})
})
public class UserEntity {
#Id
#SequenceGenerator(name = "users_id_seq", sequenceName = "users_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_id_seq")
private Long id;
#NotNull
#Column(name = "email_address", updatable = false)
private String emailAddress;
#Column(name = "preferences", columnDefinition="TEXT")
private String preferences;
When I debug through the removeProfile method on the original project, I run a query on the user_profiles table before and after I execute this.delete(entity). I can confirm the table entry for the entity passed into the removeProfile method is present before that line runs and deleted after that line runs.
When I debug the same code when it is being called from the parent project, I run the same query for the presence of the table entry for the entity passed into the removeProfile method before and after. In this case the table entry is not deleted, but I don't see any exception thrown or any other error - the row just doesn't get deleted as I would expect.
One additional wrinkle I want to mention is that I am using IntelliJ and have my local database connection configured in both projects. When I run select * from pg_catalog.pg_tables; from a postgres console in the original project, I see the user_profiles and users tables come back in the list of tables on the database. When I run the same query on the parent project, I don't see the user_profiles and users tables in the list of tables on the database, I only see the default postgres tables. Is it possible I have some sort of error based on connecting to the same local database from two separate projects? If that was the case I would expect the parent project to actually fail to connect to the database, rather than show a successful connection but quietly fail to run a database query.
For reference here is everything database related from the two project properties files:
Original project:
spring.jpa.database=default
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
datasource.platform=postgres
datasource.jdbc-url=jdbc:postgresql://localhost:5432/database
sql-dialect=org.hibernate.dialect.PostgreSQLDialect
datasource.hbm2ddl.auto=
datasource.username=user
datasource.password=
Parent project:
spring.jpa.database=default
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
datasource.platform=postgres
datasource.jdbc-url=jdbc:postgresql://localhost:5432/database
sql-dialect=org.hibernate.dialect.PostgreSQLDialect
datasource.hbm2ddl.auto=
datasource.username=user
datasource.password=
Seems possible to me the issue could be Spring related, JPA/Hibernate related, Maven related, Postgres related, IntelliJ related, or some delightful combo. Any ideas on what I'm missing here?
For reference, here are the versions I'm using:
Spring: 2.0.3
Postgres: 9.4-1206-jdbc42
Maven: 2.7
And here are our hibernate/JPA dependencies:
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
And here is the db configuration class I have:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManager",
transactionManagerRef = "transactionManager",
basePackages = {"com.repositories"}
)
public class DbConfig {
#Value("${datasource.hbm2ddl.auto}")
private String ddlAuto;
#Value("${sql-dialect}")
private String dialect;
#Bean
#ConfigurationProperties(prefix="datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManager(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource)
{
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.dialect",dialect);
properties.put("hibernate.hbm2ddl.auto",ddlAuto);
return builder
.dataSource(dataSource)
.persistenceUnit("object")
.properties(properties)
.packages("com.entities")
.build();
}
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier("entityManager") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}

Related

Quarkus Reactive with Vert.x and Hibernate Reactive / java.lang.NullPointerException: Cannot store to object array because "this.loadedState" is null

i am trying to use quarkus reactive with vert.x and hibernate reactive.
this is my pom.xml:
<quarkus-plugin.version>1.12.2.Final</quarkus-plugin.version>
and
<quarkus.platform.version>1.12.2.Final</quarkus.platform.version>
with:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
this is my application.properties file:
# postgres-configuration
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=partner_usr
quarkus.datasource.password=postgrespw
quarkus.datasource.reactive.url=vertx-reactive:postgres://localhost:3310/partnerdb
# test, but not working (schema's won't created)
quarkus.hibernate-orm.database.generation.create-schemas=true
# working (drop-and-create only on mysql, not on postgres)
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.log.sql=true
quarkus.http.cors=true
Then, i have following entities:
#Data
#MappedSuperclass
public abstract class IdEntity {
#Id
#SequenceGenerator(name = "entitySeq", sequenceName = "entitiy_id", allocationSize = 1, initialValue = 5)
#GeneratedValue(generator = "entitySeq", strategy = GenerationType.AUTO)
private Long id;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Person extends IdEntity {
private String firstName;
private String lastName;
public Person() {
}
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Address personAddress;
}
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class Address extends IdEntity {
private String street;
private String houseNumber;
private int postalCode;
private String city;
#OneToMany(orphanRemoval = true, mappedBy = "personAddress", fetch = FetchType.LAZY)
private List<Person> persons = new ArrayList<>();
public Address() {
}
}
Now, i am calling a reactive web-service with a reactive db access:
#Path("/person")
#ApplicationScoped
public class PersonResource {
#Inject
io.vertx.mutiny.pgclient.PgPool sqlClient;
#Inject
Mutiny.Session mutinySession;
#GET
//#Produces(MediaType.APPLICATION_JSON)
#Path("/list-persons")
#Route(path = "/list-persons", methods = HttpMethod.GET, produces = MediaType.APPLICATION_JSON)
#Transactional
public Multi<Person> listAllPersons() {
// return sqlClient.query("SELECT * FROM Person ORDER BY lastName ASC").execute()
// .onItem().transformToMulti(set -> Multi.createFrom().iterable(set))
// .onItem().transform(this::transformPersons);
return mutinySession.createQuery("SELECT f FROM Person f ORDER BY f.lastName")
.getResults().onItem().transform(this::transformObject);
}
private Person transformObject(Object f) {
return (Person)f;
}
private List<Object> transformPersons(Object f) {
final Person person = (PartnerMockEntity)f;
final List<Object> bogus = new ArrayList<>();
bogus.add(partner);
return bogus;
}
}
Exception:
Resulted in: com.fasterxml.jackson.databind.JsonMappingException: Cannot store to object array because "this.loadedState" is null (through reference chain: de.subito.model.Person["personAddress"]->de.subito.model.Address["person"])
I tried to use :
FetchType.EAGER on Address in Person
I removed the #OneToMany Relation in Address: this solves the error (yay), but the addresses won't be returned in the resulting json (id is existing, but the values are not fetched)
The questions is, how can i fetch in reactive those kind of relations without getting errors?
Or do i need a angular page in order to display this correctly?
Somehow i forgot about how fetchType.Lazy works.
Simply add a join fetch into the hql and everything works as expected.
SELECT p from Person p left join fetch p.personAddress
When using this query, there's no session/closed or any other exception thrown and the json result will be displayed as expected.
Additional note: in order to avoid recursive serialization, it is required to use the
#JsonManagedReference and #JsonBackReference
Annotations, depending on your needs to your relations.

Constraint violation when reading data

I am writing integration tests with junit-jupiter and something very strange is happening -> Constraint violation exception occurs when I am reading (not saving the data)
storesTemplateRepository.findByCountryOrderByTemplateName(country, pageable); raises the following exception:
could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.STORES_TEMPLATE(ID)"; SQL statement:
insert into stores_template (country, stores, template_name, id) values (?, ?, ?, ?) [23505-196]]
Entity:
#Entity
#Getter
#Setter
#NoArgsConstructor
#Table(name = "STORES_TEMPLATE")
public class StoresTemplate {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "STORES_TEMPLATE_ID_SEQ")
#SequenceGenerator(name = "STORES_TEMPLATE_ID_SEQ", sequenceName = "STORES_TEMPLATE_ID_SEQ", allocationSize = 1)
private long id;
#Enumerated(EnumType.STRING)
private CountryEnum country;
private String templateName;
#Lob
private String stores;
public void setStores(List<String> stores) {
this.stores = String.join(",", stores);
}
#JsonIgnore
public List<String> getStoresAsList() {
return Arrays.stream(stores.split(","))
.distinct()
.collect(Collectors.toList());
}
}
Test
#Slf4j
#Transactional
#SpringBootTest
public class StoresTemplateControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private StoresTemplateRepository storesTemplateRepository;
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
public void fullApiTest() {
OAuth2AuthenticationToken dePrincipal = new TestDataBuilder()
.createOAuth2AuthenticationToken()
.setDefaultStoreUser()
.setRoles(SiamRoles.DE_CREATOR_ADMIN)
.build();
CreateStoresTemplateDto createStoresTemplateDeDto = CreateStoresTemplateDto.builder()
.country(CountryEnum.DE)
.stores(List.of("de1000", "de1100"))
.templateName("de template")
.build();
CreateStoresTemplateDto createStoresTemplateBgDto = CreateStoresTemplateDto.builder()
.country(CountryEnum.BG)
.stores(List.of("bg2000", "bg2100"))
.templateName("bg template")
.build();
CreateStoresTemplateDto createStoresTemplateDefaultDto = CreateStoresTemplateDto.builder()
.country(null)
.stores(List.of("de3000", "de3100"))
.templateName("default template")
.build();
try {
// find all existing by predefined insertion script
for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
storesTemplateRepository.deleteById(id);
}
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDeDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateBgDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE); // Here the exception occurs
storesTemplateRepository.save(StoresTemplateMapper.toStoresTemplate(createStoresTemplateDefaultDto));
storesTemplateRepository.findByCountryOrderByTemplateName(CountryEnum.DE);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
}
JPA/Hibernate queues the operations in its session whenever possible, does not call the database instantly and then just before the transaction is completing, order those operations based on type and execute them. This is called Transactional write-behind in hibernate. As you can see, even though you called the delete first, hibernate will order it as last if it was queued.
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
So even though you do delete first as you can see hibernate will do it last. If you want to control the order, you need to flush it. So do the following.
for (long id: findAll(dePrincipal, CountryEnum.DE).map(e -> e.id)) {
storesTemplateRepository.deleteById(id);
}
storesTemplateRepository.flush();
Reference
Executions Order

Mybatis dao pattern not autowiring field

Trying to make a dao pattern for mybatis and spring. Want to use this sql queries anywhere i want, just using dependency injection.
When i try to use this method (.getMaxId()) it gives me "Null pointer exception".
Why field SqlSession is not autowiring(gives null)? Intellige idea shows this field as a canditate for
autowiring.
I think there is 3 steps to achieve:
1) Autowire session
2) get mapper from session
3) execute queries from this mapper
I do this
#Autowired
private Student_mapper sm;
sm.getMaxId();
Service
#Service
#Transactional
public class Student_mapperImpl {
#Autowired
private SqlSession session;
#Autowired
Student_mapper mapper = session.getMapper(Student_mapper.class);
public Integer getMaxId() {
Integer value = mapper.getMaxId();
return value;
}
}
Bean configuration file
#org.springframework.context.annotation.Configuration
#EnableTransactionManagement
#ComponentScan
public class DataSourceStudent_Mapper {
#Bean
public SqlSession getDataSource() {
String user = "postgres";
String password = "postgres";
String databasenameURL = "jdbc:postgresql://localhost:5432/postgres";
String dbDriver = "org.postgresql.Driver";
DataSource dataSource = new org.apache.ibatis.datasource.pooled.PooledDataSource(
dbDriver, databasenameURL, user, password);
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development",
transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(configuration);
SqlSession session = sqlSessionFactory.openSession();
session.getConfiguration().addMapper(Student_mapper.class);
return session;
}
}
Student_mapper - interface with a queries
#Repository
public interface Student_mapper {
#Select("select max(id) from student")
#Result(property = "id", column = "ID")
Integer getMaxId();
}
Entity
public class Student {
private int id;
private String name;
private String branch;
private int percentage;
private int phone;
private String email;
//(setters,getters, allArgs constructor are ommited)
}
I don't understand what's wrong. There is any examples how to realise this? I would like to execute my queries anywhere i want without constantly initializing the session, datasource etc. Thanks in advance
mybatis-spring Getting Started should be useful.

Spring Boot testing with H2 - Table "OAUTH_ACCESS_TOKEN" not found

I'm thinking I need to setup the db testing environment (e.g. create tables, seed users so that token can be issued with credentials) before I can run tests but not sure how to.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class UsersControllerTest {
// ...
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("client_id", CLIENTID);
params.add("grant_type", CLIENTPASSWORD);
params.add("username", username);
params.add("password", password);
ResultActions result = mockMvc.perform(post("/oauth/token")
.params(params)
.with(httpBasic(CLIENTID, CLIENTPASSWORD))
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"));
String resultString = result.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
#Test
public void givenNoToken_whenGetAllUsers_thenUnauthorized() throws Exception {
mockMvc.perform(
get("/users")
).andExpect(status().isUnauthorized());
}
#Test
public void givenToken_whenGetAllUsers_thenOk() throws Exception {
String accessToken = obtainAccessToken("martyn", "secret");
mockMvc.perform(
get("/users")
.header("Authorization", "Bearer " + accessToken)
).andExpect(status().isOk());
}
// ...
Here is a typical Entity for this app:
#Entity(name = "users")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = -8507204786382662588L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String surname;
#Column(nullable = false, unique = true)
private String email;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
#JsonIgnore
private String password;
#OneToMany
#JoinColumn(name="user_id") // cascade = CascadeType.ALL, orphanRemoval = true
private List<Fund> funds;
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
// standard getters and setters
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Fund> getFunds() {
return funds;
}
}
But also, as the error indicates, I'd need to generate these oauth* tables too.
Here is my src/test/resources/application.properties
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=sa
So I guess I want to generate the tables (entities, and oauth*) in the H2 database prior to running tests and populate with a single user(?) but can't seem to figure how this is done in Spring Boot. Or should I not be hitting any database and mocking JDBC altogether? Could someone point me in the correct direction as to how to prepare a test environment here? I'm at a bit of a loss.
UPDATE
Here is how dataSource is configured:
#Configuration
public class JDBCTokenConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(datasourceUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
return dataSource;
}
#Bean
public TokenStore tokenStore(DataSource dataSource) {
return new JdbcTokenStore(dataSource);
}
// #Bean
// public TokenStore tokenStore() {
// return new InMemoryTokenStore();
// }
}
pom.xml
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
...
</dependencies>
I think it is a good thing to reach your in memory database without mocking. Honestly, you will need more time to configure rather than creating the correct schema needed for your database.
Using Spring-boot, it is very easy to configure to test your application:
Declare using spring-boot-starter-jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Add the in memory DB for your tests
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
Delete your spring.datasource properties in application.properties
Thanks to #SpringBootApplication, the datasource will be automatically configured to connect to your H2 in memory database.
Create the SQL schema
By default, spring-boot-starter-jpa configures automatically the datasource to execute scripts classpath:/schema.sql and if you need also, classpath:/data.sql.
Create a schema.sql in src/test/resources, and create the tables (copy the following content, I think this is what you need: https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql) (I am not sure for this, maybe hibernate creates your schema on his own).
Create your schema in src/test/resources/schema.sql and seed the users in src/test/resources/data.sql
Check also the spring documentation to know how you can configure hibernate :
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#data-properties
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference
Now I understand, which is everybody, that you need to have your configuration different dependending on your environment. The best to do that is to rely on profiles.
In your situation, you have a prod profile, and a test profile.
Declare the profile and keep your spring.datasource properties in your src/test/resources/application.properties (the easier in your case I think)
I suggest you to read this Configure specific in memory database for testing purpose in Spring, and let me know if you have troubles configuring your environment.
With this, you will need to:
Add an annotation at the top of your Test class #ActiveProfiles('test')
Restore the spring.datasource properties you previously deleted an put them in src/test/resources/application-test.properties
Let me know

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

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);
}

Resources