Spring Boot JPA 2 databases - java

I am using hibernate, spring boot, jpa
First DB is MS SQL. New requirement is to connect Oracle JDBC
It is multi module application. In main module i have application-remote.yml file with DB credentials. Here it is
spring:
datasource:
driver-class-name:com.microsoft.sqlserver.jdbc.SQLServerDriver
url: ************
username: *****
password: *****
secondDatasource:
driver-class-name: oracle.jdbc.OracleDriver
url: ***********
username: ******
password: ******
jpa:
hibernate:
ddl-auto: none
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
properties:
dialect: org.hibernate.dialect.SQLServer2012Dialect
jackson:
date-format: com.fasterxml.jackson.databind.util.StdDateFormat
logging:
config: classpath:logback-remote.xml
file: /usr/share/tomcat/pqa.log
My Application config in module app, com.my.project
#Configuration
#Import({
ControllerConfig.class,
PersistenceConfig.class
})
public class ApplicationConfig {
}
My persistenceConfig in module persistence com.my.project
#Configuration
public class PersistenceConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="spring.secondDatasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
I have many entities in persistence com.my.project.entity
One of them is
#Data
#Entity
#Builder
#Table(name = "locationSelection", schema = "dbo")
public class Location {
#Id
#Column(name = "timerName")
private String timerName;
#Column(name = "center")
private String center;
#Column(name = "station")
private String station;
#Column(name = "cell")
private String cell;
#Column(name = "place")
private String place;
}
Repository for it in Persistence, com.my.project.repository
#Repository
public interface LocationRepository extends JpaRepository<Location, String> {}
And for 2nd databe in Persistence com.my.project.entityForIntegration
#Data
#Builder
#Entity
#Table(name = "PQA_IN")
public class Pqa_In {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int id;
#Column(name = "STATUS")
private String status;
#Column(name = "UPD_USER")
private String upd_user;
#Column(name = "UPD_DATE")
private Timestamp upd_date;
#Column(name = "INS_USER")
private String ins_user;
#Column(name = "INS_DATE")
private Timestamp ins_date;
}
And repository in com.my.project.repositoryForIntegration
public interface Pqa_In_repository extends JpaRepository<Pqa_In, Long> {
}
Now this is doesnt work. I have a lot of erros, with the classes and configs above the error is
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.my.project.services.impl.AnomaliesServiceImpl required a bean of type 'com.my.project.repository.AnomaliesRepository' that could not be found.
Action:
Consider defining a bean of type 'com.my.project.repository.AnomaliesRepository' in your configuration.
I have read Spring documentation, baeldung, stackoverflow and other sites with guides and questions about multiple datasources, but i cant do nothing.
Please, provide me the correct solution to connect 2 DBs.
AnomaliesServiceImpl
#Service
#Slf4j
public class AnomaliesServiceImpl implements AnomaliesService {
private AnomaliesRepository anomaliesRepository;
#Autowired
public AnomaliesServiceImpl(AnomaliesRepository anomaliesRepository) {
//
this.anomaliesRepository = anomaliesRepository;
}
#Override
public ResponseEntityDTO getAllAnomalies(int currentPageNumber, int pageSize) {
Page<WeldMeasureProt> page = anomaliesRepository.findAllAnomalies(pageable);
return convertToWeldMeasureProtDTO(page, currentPageNumber);
}
AnomaliesRepository
#Repository
public interface AnomaliesRepository extends PagingAndSortingRepository<WeldMeasureProt, WeldMeasurePointPrimaryKey> {
#Query("from WeldMeasureProt wm where wm.detection<>0 " +
"and wm.uirQStoppActCntValue=0 " +
"and wm.monitorState=0 ")
Page<WeldMeasureProt> findAllAnomalies(Pageable pageable);
}

Related

Hibernate sequence does not exist error after adding second Database source

I face the following error after adding a second datasource in my project:
Table 'portal-titan.hibernate_sequence' doesn't exist; error performing isolated work; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: error performing isolated work
It appears when I try to INSERT an object with type, including GenerationType.AUTO. I am a bit confused because there are a lot of questions and discussions for this topic and I tried a lot, but I do not get the desired result. It starts working when I change the GenerationType to IDENTITY, but I read that this can lead to performance issues, which is not the desired result. Something more, I have use-new-id-generator-mappings: false in my hibernate properties in yml file, but this does not help solving the problem, too.
Here it is my yml file:
management:
security:
roles: ADMIN
context-path: /management
spring:
messages:
basename: i18n/messages
mvc:
favicon:
enabled: false
thymeleaf:
mode: XHTML
jpa:
hibernate:
ddl-auto: validate
use-new-id-generator-mappings: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
format-sql: true
physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
caching:
specs:
products:
timeout: 60
orders:
timeout: 60
max: 500
deliveries:
timeout: 120
tracking:
timeout: 1
admins:
timeout: 120
balance:
timeout: 120
application:
async:
core-pool-size: 2
max-pool-size: 50
queue-capacity: 1000
jwt:
token-secret: secret-key
token-validity: PT6H
token-remember-me-validity: P7D
default-language-tag: bg
upload:
allowed-content-types:
- image/jpg
- image/jpeg
- image/png
static-resource:
path: /static/
jobs:
batch-size: 20
activity:
purge:
ttl-value: 90
ttl-unit: days
job-run-interval-value: 1
job-run-interval-unit: days
Here it is how the entity which does now want to insert looks:
#Getter
#Setter
#Entity
#Table(name = "comments")
public class Comment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false, unique = true)
private String uuid;
#Column(nullable = false)
private String content;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "delivery_id")
private Delivery delivery;
#CreatedDate
#Column(name = "created_at", nullable = false)
private Instant createdAt = Instant.now();
#LastModifiedDate
#Column(name = "updated_at", nullable = false)
private Instant updatedAt = Instant.now();
}
And this is the method for inserting in the controller part:
#PostMapping("/{deliveryUuid}")
#ApiOperation(value = "Submit a comment")
#ApiResponses(
value = {
#ApiResponse(code = 201, message = "Comment created"),
#ApiResponse(code = 400, message = "Validation failed")
})
#PreAuthorize("hasRole('ROLE_CUSTOMER')")
#ResponseStatus(value = HttpStatus.CREATED)
public void submitComment(
#PathVariable("deliveryUuid") String deliveryUuid,
#Valid #RequestBody CommentDto commentDto,
#CurrentUser AuthUser principal) {
commentService.submitComment(commentDto, deliveryUuid, principal);
}
Because the error starter to appear after I configured second database, I am adding their code too. Comment entity is in the primary database.
Primary:
#Configuration
#EnableTransactionManagement
#EnableJpaAuditing
#EntityScan(basePackageClasses = {TitanClientApp.class})
#EnableJpaRepositories(
entityManagerFactoryRef = "clientEntityManagerFactory",
transactionManagerRef = "clientTransactionManager",
basePackages = { "titan.client" }
)
public class DbConfiguration {
#Primary
#Bean(name="clientDataSource")
#ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource clientDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "clientEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean clientEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("clientDataSource") DataSource clientDataSource) {
return builder
.dataSource(clientDataSource)
.packages("titan.client")
.build();
}
#Primary
#Bean(name = "clientTransactionManager")
public PlatformTransactionManager clientTransactionManager(
#Qualifier("clientEntityManagerFactory") EntityManagerFactory clientEntityManagerFactory) {
return new JpaTransactionManager(clientEntityManagerFactory);
}
}
Secondary:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "gpsEntityManagerFactory",
transactionManagerRef = "gpsTransactionManager",
basePackages = {"titan.gps"}
)
public class SecondaryDbConfiguration {
#Bean(name = "gpsDataSource")
#ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource gpsDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "gpsEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean gpsEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("gpsDataSource") DataSource gpsDataSource) {
return builder
.dataSource(gpsDataSource)
.packages("titan.gps")
.build();
}
#Bean(name = "gpsTransactionManager")
public PlatformTransactionManager gpsTransactionManager(
#Qualifier("gpsEntityManagerFactory") EntityManagerFactory gpsEntityManagerFactory) {
return new JpaTransactionManager(gpsEntityManagerFactory);
}
}
Your second database is simply lacking a table that Hibernate needs to work correctly. You have to create that table if you want to use table based sequences, which is kind of the default.
Using IDENTITY is totally fine though as long as you don't insert thousands of records per second.

Transactional Service Method that updates 2 repositories

I want to test Transactional operation in my project. Basically, I want to roll back the userService.saveUser() operation, if an exception is thrown. I have simplified the classes, and you can find it below.
A user must live in an address. An address can have multiple users.
Address Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Address {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "STREET")
#NotNull
private String street;
#ToString.Exclude
#OneToMany(mappedBy = "address")
private Set<User> users = new HashSet<>();
}
User Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "FIRSTNAME", nullable = false)
#NotNull
private String firstName;
#ManyToOne(fetch = FetchType.LAZY)
private Address address;
}
Repositories
public interface AddressRepository extends CrudRepository<Address, Long> {
}
public interface UserRepository extends CrudRepository<User, Long> {
}
UserService Class
#Service
#Slf4j
#AllArgsConstructor
public class UserService {
#Autowired
AddressRepository addressRepository;
#Autowired
UserRepository userRepository;
#Transactional
public void saveUser(String firstName, String street) {
var address1 = Address.builder.street(street).build();
// to make sure that I have "id" of the address when I am saving it.
var addressSaved = addressRepository.save(address1);
if ("f1".equals(firstName))
throw new RuntimeException("some exception");
var user = User.builder()
.firstName(firstName)
.address(addressSaved)
.build();
// this operation can also throw DataIntegrityViolationException
userRepository.save(user);
}
}
This is my test class
#SpringBootTest
class UserServiceIT {
#Autowired
AddressRepository addressRepository;
#Autowired
UserRepository userRepository;
#Autowired
UserService userService;
#BeforeEach
void beforeEach() {
userRepository.deleteAll();
addressRepository.deleteAll();
}
#Test
void test_saveUser() {
assertThrows(RuntimeException.class,() -> userService.saveUser("f1", "s1"));
assertEquals(0, userRepository.count());
assertEquals(0, addressRepository.count());
}
#Test
void test_saveUser2() {
// column: nullable = false will throw the exception
assertThrows(DataIntegrityViolationException.class,() -> userService.saveUser(null, "s1"));
assertEquals(0, userRepository.count());
assertEquals(0, addressRepository.count());
}
}
Both of the tests give assertion error on address count (Address is saved and user is not saved). I expect address to be roll backed (and not to be saved) since there is an error after saving the address, and while saving the user (some condition is violated, therefore 2 saves must be roll backed). What am I doing wrong?
application.yml for test environment
spring:
devtools:
restart:
enabled: false
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=false
driverClassName: org.h2.Driver
username: sa
password: 123
h2:
console:
enabled: false
jpa:
database-platform: org.hibernate.dialect.H2Dialect
database: H2
show-sql: false
hibernate:
ddl-auto: create
You can reach the whole sample project from this link: https://wetransfer.com/downloads/7cb870266e2e20f610b44d3cc9f229c220220308071438/7b88a2700076a3e53771e389c796cfe420220308071438/c777ab
The code you posted here differs from what is actually exists in the original code you uploaded.
original code:
#Transactional
void saveUser(String firstName, String street) {
var address = Address.builder().street(street).build();
var addressSaved = addressRepository.save(address);
if ("f1".equals(firstName))
throw new RuntimeException("f1");
var user = Person.builder()
.firstName(firstName)
.address(addressSaved)
.build();
personRepository.save(user);
}
This method actually have default access modifier so CGLIB is not able to override it and creates the needed logic. change access modifier of this method to public

Pass #Transactional method result from Service to Controller Layer Spring Boot

I'm trying to lazily fetch a ManyToMany relationship (Courses - Students) from the Service and pass the result to the Controller. While i'm in the Service, no LazyInitializationException is thrown, thanks to the #Transactional annotation. However, while i'm in the Controller the LazyInitializationException is thrown (while getting Course.students), because the Session was closed. How can i resolve this issue, without eagerly fetch the Collection? That's my code: Couse Model
#Entity
#Getter
#Setter
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany
#JoinTable(name = "COURSES_STUDENTS",
joinColumns = {#JoinColumn(name = "COURSE_ID")},
inverseJoinColumns = {#JoinColumn(name = "STUDENT_ID")})
private Set<Student> students;
public Course() {
this.students = new HashSet<>();
}
Student Model
#Entity
#Getter
#Setter
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "students")
private Set<Course> courses;
public Student() {
this.courses = new HashSet<>();
}
}
Course Repository
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}
Course Service
#Service
public class CourseService {
private final CourseRepository courseRepository;
#Autowired
public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#Transactional
public ResponseEntity<List<Course>> findAll() {
return this.courseRepository.findAll().isEmpty() ? ResponseEntity.noContent().build()
: ResponseEntity.ok(this.courseRepository.findAll());
}
}
Course Controller
#Controller
#RequestMapping("/")
public class CourseController {
private final CourseService courseService;
#Autowired
public CourseController(CourseService courseService) {
this.courseService = courseService;
}
#GetMapping
public ResponseEntity<List<Course>> index() {
return this.courseService.findAll();
}
}
application.properties
spring.datasource.url=jdbc:h2:~/database;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.open-in-view=false
spring.mvc.hiddenmethod.filter.enabled=true
logging.level.org.springframework.web=DEBUG
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Thanks in advance.
So there are 2 approaches :
What is this spring.jpa.open-in-view=true property in Spring Boot?
This is bad for performance and must be avoided at all costs.
use jpql queries to join fetch lazy collections needed in DAO layer so they are available in the controller when you need them to be.
All in all, do not use transactional to keep the db session open to fetch lazy collections. Just join fetch lazy collections in db / dao layer to have the data needed for each endpoint available.
If you want have a look here for how to use join fetch How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller

org.hibernate.hql.internal.ast.QuerySyntaxException: Apartment is not mapped [from Apartment]

I have springboot rest appplication with Hibernate and MySQL. I have this error:
nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.hql.internal.ast.QuerySyntaxException: Apartment is not
mapped [from Apartment]
But I dont know where I have a mistake. I have two tables: Apartments and Residents in DB. But now I try only getAllApartments() method. I use Intellij and I even checked my DB in her. And I have little picture near my Entity class, where I have correct data source. And I think that I checked names my class and fields.
This is my Entity:
#Entity
#Table(name = "Apartments")
public class Apartment {
#Column(name = "apartment_id")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer apartmentId;
#Column(name = "apartment_number"
private Integer apartmentNumber;
#Column(name = "apartment_class")
private String apartmentClass;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH},
mappedBy = "apartment")
List<Resident> residentList;
My DAO method:
#Repository
public class ApartmentDAOImpl implements ApartmentDAO {
#Autowired
private EntityManager entityManager;
#Override
public List<Apartment> getAllApartment() {
Session session = entityManager.unwrap(Session.class);
Query query = session.createQuery("from Apartment");
List<Apartment> apartmentList = query.getResultList();
return apartmentList;
}
My Controller:
#RestController
#RequestMapping("/api")
public class ApartmentController {
#Autowired
ApartmentService apartmentService;
#GetMapping("/apartments")
public List<Apartment> getAllApartments() {
List<Apartment> apartmentList = apartmentService.getAllApartment();
return apartmentList;
}
I also have service layer without any logic.
My property.file
spring.datasource.url=jdbc:mysql://localhost:3306/correct_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=projuser
spring.datasource.password=projuser
Give me advice, please.
Maybe, As I used multi module application, Hibernate or Spring didn't see my Entity.
And I clearly indicated my Entity class with #EntityScan(basePackages = {"com.punko.entity"}) under my SpringBootApplication class:
#SpringBootApplication(scanBasePackages = "com.punko")
#EntityScan(basePackages = {"com.punko.entity"})
public class SpringBootApplicationConfig {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationConfig.class, args);
}

Getting 404 Error when calling a Spring Boot API

I wrote a few request mappings and connected my Spring Boot application to a Postgresql DB using JPA. However, when I try to call the API, I get the following message:
{"timestamp":"2021-01-30T21:58:34.028+00:00","status":404,"error":"Not Found","message":"","path":"/api/v1/sessions"}. I tried printing a message when the API is called and it works so I think it might have something to do with the JPA connection? (I also tested if the DB and credentials are good using SQL Shell, and they are ok)
My model:
#Entity(name = "sessions")
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long session_id;
private String session_name;
private String session_description;
private Integer session_length;
#ManyToMany
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn(name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id"))
private List<Speaker> speakers;
My controller:
#Controller
#RequestMapping("/api/v1/sessions")
public class SessionsController {
#Autowired
private SessionRepository sessionRepository;
#GetMapping
public List<Session> list() {
System.out.println("Get method called");
return sessionRepository.findAll();
}
#GetMapping
#RequestMapping("{id}")
public Session get(#PathVariable Long id) {
return sessionRepository.getOne(id);
}
My repository:
public interface SessionRepository extends JpaRepository<Session, Long> {
}
And lastly, my application properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/databaseName
spring.datasource.username=postgres
spring.datasource.password=mypasswordhere
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.show-sql=true
#GetMapping(value = "/{id}")<---remove Request mapping and also add the slash here
public Session get(#PathVariable Long id) {
return sessionRepository.getOne(id);
}

Categories

Resources