how to align column names in Postgres Spring-data and liquibase? - postgresql

I was trying to follow this tutorial to set up a simple spring boot app with postgres. They used Docker to set up the db, and liquibase. I have used spring-data and JPA before but not postgres or liquibase.
I was trying to set up a simple Student db. Here's my entity:
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String firstName;
#NotNull
private String lastName;
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
Here's the changelog for liquibase
create table "student" (
id bigserial not null,
firstName varchar(50) not null,
lastName varchar(50) not null,
primary key (id)
);
I am loading a few students via a class implementing CommandLineRunner, just to have some initial data
Upon startup, I get the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: column "first_name" of relation "student" does not exist
What is changing the column of firstName into first_name?
I could use #Column("first_name") in the Student entity but then that doesn't seem to line up with the table definition in the changeset.
I don't know why liquibase needs the table definition, with JPA there. I thought it was easier without it, by just letting JPA create the table from the #Entity.
Bigger picture is I'm just trying to do a proof of concept/scaffold for a quick spring boot app with postgres on the backend and ReactJS on the front end. This tutorial might be better for that, if I could get the postgres back end figured out. Don't really need docker, but I thought that would make it easier. But now that and liquibase seem to be creating a problem by persisting a db and table that don't match up.

Related

JPA: Update mapping table alone

I have a Project and Employee entities, which has ManyToMany relationship like below.
#Entity
public class Project {
#Id #GeneratedValue
private int projectId;
private String projectName;
// has some additional columns
#ManyToMany(mappedBy = "projects")
private List<Employee> emp = new ArrayList<Employee> ();
....
.....
}
#Entity
public class Employee {
#Id #GeneratedValue
private int id;
private String firstName;
private String lastName;
#ManyToMany(cascade=CascadeType.ALL)
List<Project> projects = new ArrayList<Project> ();
....
....
}
When I use above entities, JPA create a mpping table 'Employee_Project' like below.
create table Employee_Project (emp_id integer not null, projects_projectId integer not null)
My question is, whenever new employee is added, I want to update both employee table and Employee_Project mapping table only, assume I know project id that I would like to map this employee to. (without touching project table/entity, I mean why should I provide complete project object, while saving employee entity alone, how can I do this via jpa?)
You don't need to provide the entire Project object. Use EntityManager.getReference(projectId) or JpaRepository.getOne(projectId).
Those methods will create a proxy object with the appropriate id, rather than loading the entire Project entity from the data store.
EDIT Your service method should look pretty much like the following:
#Transactional
public void createEmployee(Employee employee, Long projectId) {
employee.setProjects(List.of(projectRepository.getOne(projectId));
employeeRepository.save(employee);
}
As a side note, CascadeType.ALL (in particular, because it includes CascadeType.MERGE and CascadeType.REMOVE) doesn't make sense for #ManyToMany. Unless you're planning to create a Project by creating an Employee, CascadeType.PERSIST makes no sense, either.

save List<String> by jpa without create new table

Class definition
#Entity
#Table(name = "t_domain")
public class Domain(){
#Id
String id;
String fieldA;
String fieldB;
String fieldC;
List<String> operations;
}
Table definition
CREATE TABLE `t_domain` (
`id` varchar(38) ,
`fieldA` varchar(255) ,
`fieldB` varchar(255) ,
`fieldC` varchar(255) ,
`operations` varchar(255) ,
PRIMARY KEY (`id`)
)
JSON
{"id":"1",
"fieldA":"a",
"fieldB":"b",
"fieldC":"c",
"operations":["a","b"]}
From this page['https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection'],said The ElementCollection values are always stored in a separate table..
In jpa 2.0,#ElementCollection is a way to save collection,but it seen to need a new table to store collection value.
Question:
I dont want to create any new table like domain_operation or another_table_name defind in #CollectionTable(name="another_table_name").
I want to save the json to mysql in only one row.
I'm using Hibernate 4.3.11
Ignore the below, it refers to original version of the question
#ElementCollection
#CollectionTable(
name="t_domain",
joinColumns=#JoinColumn(name = "id", referencedColumnName = "id")
)
#Column(name="operations")
Without specifying table name, Hibernate uses entityName_collectionName, which in your case gives domain_operation
Have you try using manually a serializator?
String operations;
#Transient
private ObjectMapper mapper;
public void setOperationsList(List<String> in) throws JsonProcessingException {
operations = mapper.writeValueAsString(in);
}
public List<String> getOperationsList() throws IOException {
return mapper.readValue(operations, new TypeReference<List<String>>(){});
}
}

JPA Add extra fields by calling a function

How can I add an extra column in JPA query?
Suppose we have a table "users" created by following pseudocode:
create table users(name,birthdate);
User jpa entity is something like this :
#Entity
#Table("users")
public class User {
#Basic
#Column
private String name;
#Basic
#Column
private Date birthDate;
#Transient
private int age;
//getters and setters are here
}
Note that the Age field is not exsists in the table
I want to write the following query in JQL using
entityManager.createQuery(jql)
and calculate age over the database
select u.*, sysdate-u.birthdate age from users
The problem is solved.
The query text is like :
String jql = "select NEW entity.User(u.id, u.name, u.birthDate, sysdate-u.birthDate as age) from User u";
and also a appropriate constructor is added to the entity
public User(int id, String name, Date birthDate, double age) {
this.id = id;
this.name = name;
this.birthDate = birthDate;
this.age = age;
}
and uses entitymanaget.createQuery(jql) instead of createNativeQuery
Usully i solve this problem using this way :
select new name.of.package.users(u.*, sysdate - u.birthdate as age) from users u;
Edit
It leads to ORA-00923: FROM keyword not found where expected My Query
text is : String jql = "select new entity.User(u.*, sysdate-u.birthdate as age) from User u";
The query contain an error in :
select new entity.User(u.*, sysdate-u.birthdate as age) from User u
//--------------------------------^^^
Possibly using a DB-View might help. Do you want the entity to be updateable? Would it be reasonable to have a "readonly"-entity derived from a kind of "maintenance"-entity which would contain the calculated columns by using a database-view as #Table?
create view usersview as name, birthdate, sysdate - birthdate as age from users;
Entity:
#Entity
#Table("usersview")
public class UserExtended extends User {
If you don't want two different Entity-Classes (even when one is derived from the other), there would be some exploration necessary how JPA allows updating on views, if the dbms supports it (oracle does), I have no experience in that, sorry.

Schema-validation: missing table [game]

I think it may be possible dupplicate of this: Schema-validation: missing table [hibernate_sequences] but I can't figure it out.
So in my application.properties file I have this option: spring.jpa.hibernate.ddl-auto=validate and I receive this error:
Schema-validation: missing table [game]
Why I am receiving this?
Here is my Game class and User class:
Game:
#Entity
public class Game {
#Id
#Column(name = "GAME_NUMBER")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long gameNumber;
private int playerScore;
private int NPCScore;
private Date datetime;
#ManyToOne
#JoinColumn(name="USER_ID")
private User user;
public Game() {}
public Game(int playerScore, int nPCScore, Date datetime) {
super();
this.playerScore = playerScore;
this.NPCScore = nPCScore;
this.datetime = datetime;
}
public User getUser() {
return user;
}
} + getters & setters
User:
#Entity
public class User {
#Id
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long userId;
private String username;
private String password;
#OneToMany(mappedBy="user",cascade=CascadeType.ALL)
private List<Game> games;
#ElementCollection
private List<Date> startSessions;
public User() {}
public User(String username, String password, List<Game> games, List<Date> startSessions) {
super();
this.username = username;
this.password = password;
this.games = games;
this.startSessions = startSessions;
}
}
validate validates that the entities are compatible against the target, to a degree it's not foolproof. Anyway, whatever database you are trying to validate against does not have a table called game in which to store the entities.
This answer goes into more detail about what validate does.
Hibernate - hibernate.hbm2ddl.auto = validate
specifically,
checks the presence of tables, columns, id generators
Without knowing your database/expectations (are you expecting it to be created, or using Flyway/Liquibase to create/update the database etc.) I can't answer if validate is correct for your use case.
You could try create-drop to create and drop the table on startup/shutdown, but this isn't a solution for any production control over a database.
I got the same as I changed to Hibernate 5.4.0.Final.
Either Hibernate suddenly has problems to recognize the default schema or the driver does not return the schema properly.
I was able to bypass it by either adding the schema definition to the table definition.
#Table(name = "GAME", schema = "PUBLIC")
or by adding a default schema in persistence.xml.
<property name="hibernate.default_schema" value="PUBLIC" />
Don't forget permissions:
GRANT select, insert, update, delete, alter ON table_name TO usr_name;
This error can appear while using spring boot with flywayDB.
The issue might be due to the wrong naming convention of script files, which were used by flywayDB.
https://flywaydb.org/documentation/migrations#naming
The SQL standard requires names stored in uppercase.
If you named the table/fields in lowercase - JPA can automatically convert case to upper and trying to search names in this case, but write to logs in lower ¯\_(ツ)_/¯
Add this in application.yml:
spring:
jpa:
properties:
hibernate:
default_schema: game
Hibernate version 5.6.9,
Case-sensitive implementation:
hibernate:
physical_naming_strategy: 'org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl'

ebean unidirectional #OneToOne relation with unique constraint

I have a User class:
#Entity
public class User extends Model {
#Id
public Long id;
public String email;
public String name;
public String password;
}
and a driver class
#Entity
public class Driver extends Model {
#Id
public Long id;
#OneToOne (cascade = CascadeType.ALL)
#Column(unique = true)
public User user;
}
I want to make sure that the user_id is unique inside the Drivers table. But the code above does not enforce that. (I can create multiple drivers with the same user id).
Ideally, I do not want to add the #OneToOne relations in the User class because there are several different roles inside my app (e.g. driver, teacher, agent etc.) and I don't want to pollute user class with all those relations.
How can I achieve this?
I have tried this code on the model for me, and it worked. One thing to be noted, that you must use #OneToOne annotation to let the ORM knows that you have foreign key reference to other model.
The model look like following:
#Entity
// add unique constraint to user_id column
#Table(name = "driver",
uniqueConstraints = #UniqueConstraint(columnNames = "user_id")
)
public class Driver extends Model {
#Id
public Long id;
#OneToOne
#JoinColumn(name = "user_id")
public User user;
}
It will generate evolution script like this :
create table driver (
id bigint not null,
user_id bigint,
constraint uq_driver_1 unique (user_id), # unique database constraint
constraint pk_driver primary key (id)
);
So, with this method you can make sure that you will have unique user reference on driver table.
Additional Info
Because there is an additional constraint, that is not handled by framework but by the database applied on the model (such as the unique constraint), to validate the input or handling the occurred exception, you can surround Model.save() or form.get().save() expression (saving-the-model) with try-catch block to handle the PersistenceException.