How to Seed Data to H2 DB in Spring Boot App with JPA - spring

A little bit of background:
I work on a Spring boot app in a large corporation and we have no control over the Database Schema, table names, and column names. Because of this, our table and column names have no obvious meaning to them.
We are currently seeding data with the schema.sql and data.sql files in the resources directory. This hasn't been working for our team, because of the effort to seed the data with these obscure table and column makes. We often end up looking through our QA server for an account, then writing our code against a QA database.
My question:
How do I keep the schema.sql and data.sql files, but enable our team to seed data to the H2 database by using a seeder made available by JPA or Spring Data JPA.
I found a few examples of using JPA to seed data, but they don't mention where the files should be stored or how the files get called by the Spring Boot app on start up.
Our application only pulls data, and never inserts, so will I have to override the save function from the JpaRepository in order to accomplish this? Or can I just create an entity, and call the JpaRepository's save function?
Here is an obfuscated example of our account entity and repository:
AccountEntity.java
#Data
#Entity
#Table(name = "Table_Name")
#SecondaryTables({
#SecondaryTable(name = "Table_Name2", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name3", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") }),
#SecondaryTable(name = "Table_Name4", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "ACCT_ID", referencedColumnName = "ACCT_ID") })
})
public class AccountEntity {
#Column(name = "ACCT_ID")
#Id
private Integer accountIdNumber;
#Column(name = "SOME_OTHER_COLUMN1")
private String someOtherColum1;
#Column(name = "SOME_OTHER_COLUMN2", table = "Table_Name3")
private String someOtherColum2;
#Column(name = "SOME_OTHER_COLUMN3", table = "Table_Name4")
private Integer someOtherColum3;
...
}
AccountRepository.java
#Repository
public interface AccountRepository extends JpaRepository<AccountEntity, Integer> {
public AccountEntity findByAccountIdNumber(Integer accountNumber);
public List<AccountEntity> findAllByProp1Prop2AndProp3(
String prop1, String prop2, String prop3);
}

Related

Spring Boot JPA poor performance

I have a simple entity with two fields
#Entity
#Table(name = "lctn_locations")
public class LocationEntity {
#Id
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#ElementCollection
#CollectionTable(
name = "lctn_location_devices",
joinColumns = #JoinColumn(name = "location_id", referencedColumnName = "id")
)
#Column(name = "device_id")
private Set<UUID> devices = new LinkedHashSet<>();
The lctn_locations table contains 780 entries. The lctn_location_devices table just 11 entries.
I fetch 20 Locations and convert the Entity classes to my DTOs like this:
public Location map(LocationEntity locationEntity) {
Location location = new Location();
location.setId(locationEntity.getId());
long c2 = System.currentTimeMillis();
Set<UUID> devices = locationEntity.getDevices();
for(UUID deviceId : devices) {
location.getDevices().add(deviceId);
}
logger.info("Devices in {}ms", System.currentTimeMillis()-c2);
return location;
}
My issue is that mapping of devices takes ~500ms per location.
Setting the devices field on the Entity to FetchType.EAGER doesnt help much, as then the initial loading from the DB is slower.
The lctn_location_devices has two indexes:
create index location_devices_location_id_device_id
on lctn_location_devices (location_id, device_id);
create index lctn_location_devices_location_id_index
on lctn_location_devices (location_id);
I am not experienced enough with JPA, am I doing anything wrong?
A place to starte might be to log the sql the app sends to the database by adding the following to your application.properties:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
This will not give you the parameters in the sql, but you might be able to replace the ? in the sql with the relevant values.
Then you can try to run the sql from database tool to see if it is slow then too.
If so, you can try to run explain plan in your database. How do to that can depend on what database you are on. Explain plan will give you some info about which part of your sql which have the highest cost to execute and might give you some info on where to optimize your code.
If you want to look into explain plan you might give a search for explain plan on youtube a shot.

Spring MapsId not resolving target entity

I have such a case where I need to have internally many-to-one using hibernate proxies and only id externally, here using MapsId. The issue appears when I try to save something, because the target entity is not fetched, when I set the value only on the id.
Let's take an example: I have an Account table and DeviceConfig table. Inside the DeviceConfig's class definition, I add account in a many-to-one relation and accountId in relation with #MapsId.
Now when creating, I always set a value to accountId, but never the value is picked up, and the backend throws an SQL error, because the field cannot be null.
#Table(name = "djl_device_config")
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
public class DeviceConfig extends CoreEntity {
...
#JsonIgnore
#ManyToOne
#MapsId("accountId")
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "account_id", insertable = false, updatable = true, nullable = true)
private UUID accountId;
}
So I suppose this is a config error on my side, but I've been reading the JPA for these three days and I still don't know what's wrong or what I should do to achieve the behaviour I expect.
That for any help you'll provide.

Spring data jpa does not update existing row (Oracle) when using #sequencegenerator

New to spring data JPA. I have sequence created in oracle table. I am using JPARepository saveall() method to save the data. Inserts work without any problem but when i try to update any existing rows in the table it always tries to insert throwing unique constraint error since i have unique index created for that table.
Entity Class
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator1")
#SequenceGenerator(sequenceName = "emp_seq", name = "generator1",allocationSize = 1)
#Column(name = "USER_ID")
private Long id;
Save method invocation
public void persistEmployees(List<Employee> employees) {
employeeRepo.savaAll(employees);
}
Repository class
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> { }
How can i tell JPA to look for existing row before inserting? Any help is much appreciated!!!
In My Humble Opinion;
Using "Exists" condition-check as "sub-query" for all constrained columns, before Update will solve this.

Spring Boot many to many post method not updating data

My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);

Hibernate Sequence Conflict after using Audited annotation

I'm using hibernate in my spring boot application
my domain model is like this
#Entity
#Table(name = "skill")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "skill")
#Audited
public class Skill implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
}
The increment size of sequence is 50 and is working properly
but when I add Envers Audited annotation I see this error
conflicting values for 'increment size'. Found [50] and [1]
How can I resolve this conflict?
This doesn't sound like an Envers problem but a general mapping problem.
When you add an #Audited annotation, that simply informs Envers that it should inspect that particular entity mapping during Hibernate bootstrap and create the necessary audit objects to store the entity state during each transaction.
The generated Envers objects use their own sequence generators and primary key. The user defined generation strategy, sequences, etc are all ignored in the Envers object because the associated column is meant to just be a copy/pass-thru value, nothing special.
In other words, the Envers table would have a PK defined that mirrors this POJO:
#Embeddable
public class EnversSkillId implements Serializable {
#Column(name = "REV", nullable = false, updatable = false)
private Integer rev;
#Column(name = "id", nullable = false, updatable = false)
private Long id;
}
When Envers generates the audit record, it automatically uses its internal sequence generator to get the next value and assign it to EnversSkillId#rev and copies your entity's id value directly into the EnversSkillId#id property.
So as mentioned in the comments, your problem is very unlikely related to Envers.

Resources