I am working on a java project that uses Gradle, Spring, Spring Security, JPA and an H2 database. I am trying to use JPA persistence to implement database tables for role, user and usecase-table.
This is my applications.properties
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.url=jdbc:h2:~/testdb;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE
Here is my #Entity User
#Entity
#Table(name = "user")
public class User {
public User() {
}
public User(Long id, String email, String password, String fullname, boolean enabled){
this.id = id;
this.email=email;
this.password=password;
this.fullname=fullname;
this.enabled=enabled;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
private String fullname;
private boolean enabled;
#ManyToMany
#JoinTable(name = "users_roles", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
Here is my #Entity Role
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String role;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
Here is one of my repositories for User, the others are pretty much the same:
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(final String email);
}
Here is MyCapecApp.java
#SpringBootApplication
public class MyCapecApp {
private static final Logger log = LoggerFactory.getLogger(MyCapecApp.class);
public static void main(String[] args) {
SpringApplication.run(MyCapecApp.class, args);
}
#Bean
CommandLineRunner init(RoleRepository roleRepository) {
return args -> {
Role adminRole = roleRepository.findByRole("ADMIN");
if (adminRole == null) {
Role newAdminRole = new Role();
newAdminRole.setRole("ADMIN");
roleRepository.save(newAdminRole);
}
Role userRole = roleRepository.findByRole("USER");
if (userRole == null) {
Role newUserRole = new Role();
newUserRole.setRole("USER");
roleRepository.save(newUserRole);
}
log.info("Roles found with findll():");
log.info("----------------------------------");
for(Role rol : roleRepository.findAll()){
log.info((roleRepository.toString()));
}
log.info("");
};
}
And these are the errors I am getting.
2020-04-21 16:34:21.668 WARN 16560 --- [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "drop table role if exists" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "drop table role if exists" via JDBC Statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Cannot drop "ROLE" because "FKT4V0RRWEYK393BDGT107VDX0X" depends on it; SQL statement:
drop table role if exists [90107-200]
And this is the next error.
2020-04-21 16:34:21.678 WARN 16560 --- [ main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "create table user (id bigint not null, email varchar(255), enabled boolean not null, fullname varchar(255), password varchar(255), primary key (id))" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table user (id bigint not null, email varchar(255), enabled boolean not null, fullname varchar(255), password varchar(255), primary key (id))" via JDBC Statement
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "USER" already exists; SQL statement:
create table user (id bigint not null, email varchar(255), enabled boolean not null, fullname varchar(255), password varchar(255), primary key (id)) [42101-200]
The errors are the same for all three tables. I connected my database 'testdb' in the database window in intellij. Previously, i had created the three tables in the database window for user, role and usecase-table, of the same name, but i still got errors, and i was under the impression that JPA did that for you when you run the program. If that is the problem then perhaps i just made the table wrong? If so can someone please tell me the right way to do it? When i boot the project on localhost everything runs, but user input is not being stored in the database tables.
This is the first time i have ever done any project using spring or databases or of such complexity, so i apologize for my ignorance in advance. I am a student, and I really want to learn this. Please let me know if there is anything else i should include. Thanks again and I really appreciate any help you guys can offer me, and I am very grateful for your patience and your time.
Related
In a project with Spring Boot and Spring JPA I an enitity FunctionConfiguration with a list of Integer target_device
#Entity
#Table(name = "function_configuration")
public class FunctionConfigurationEntity {
#Id
#Column(name = "id_function_configuration")
#GeneratedValue(strategy = IDENTITY)
private Integer idFunctionConfiguration;
#ElementCollection
#CollectionTable(name = "target_device")
private List<Integer> targetDevice;
}
If I try to do a delete with an id of a functionConfiguration
#Transactional
#Modifying
#Query(value = "DELETE FROM FunctionConfigurationEntity fce WHERE fce.idFunctionConfiguration = idFunctionConfiguration")
void deleteByFunctionConfigurationId(#Param("idFunctionConfiguration") Integer functionConfigurationId);
I get the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: UPDATE or DELETE statement on table "function_configuration" violates foreign key constraint "fk69tox326tcrs3wed1neufb0pv" on table "target_device"
How can I fix it?
I'm trying to make a simple Spring application but I have trouble when I pass from H2 database to MySQL.
Here is my properties:
spring.datasource.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=...
spring.datasource.password=...
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=update
The entity that doesn't work:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "Option")
public class Option {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
long id;
#ManyToOne
#JoinColumn(name = "question_id", nullable = false)
private Question question;
#Column(name = "option", length = 1000, nullable = false)
private String option;
#Column(name = "is_right", nullable = false)
private boolean isRight;
}
And the error:
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table option (id bigint not null, is_right bit not null, option varchar(1000) not null, question_id bigint not null, primary key (id)) engine=MyISAM" via JDBC Statement
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.4.21.Final.jar:5.4.21.Final]
at ....
What is wrong ? I have nothing special or tricky in my entity
I solve my error by myself,
Option is a keyword in MySql that's why it didn't work
I've the following db schema:
account -< account_role >- role
Summing up: an account can be tied to multiple roles and account_role is a join table. roles is predefined and roles are inserted to DB in a migration.
Unfortunately on new account creating hibernate tries to insert the role to the table as well which results in the following exception:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "role_role_name_key"
Detail: Key (role_name)=(CUSTOMER) already exists.
How can I configure the relation to prevent the insertion?
Below mentioned classes:
Role
import javax.persistence.*;
import static javax.persistence.EnumType.STRING;
#Entity
#Table(name = "ROLE")
class Role {
#Id
#Column(name = "ROLE_NAME")
#Enumerated(STRING)
private RoleName role;
//getters, setters, constructors
}
and
Account
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
import static javax.persistence.FetchType.EAGER;
import static javax.persistence.GenerationType.SEQUENCE;
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#SequenceGenerator(allocationSize = 1, sequenceName = "ACCOUNT_PK_SEQ", name = "ACCOUNT_PK_SEQ")
#GeneratedValue(generator = "ACCOUNT_PK_SEQ", strategy = SEQUENCE)
#Column(name = "ID")
private Long id;
#Column(name = "USERNAME")
private String username;
#ManyToMany(fetch = EAGER)
#JoinTable(
name = "ACCOUNT_ROLE",
joinColumns = #JoinColumn(name = "USERNAME", referencedColumnName = "USERNAME"),
inverseJoinColumns = #JoinColumn(name = "ROLE_NAME", referencedColumnName = "ROLE_NAME")
)
private Set<Role> roles = new HashSet<>();
//getters, setters, constructor
}
Here is the code responsible for saving new account:
#Transactional
public Account createNewAccount(Account account, String password) {
validator.validateNewAccount(account, password);
String email = account.getUsername().toLowerCase();
checkIfEmailAlreadyTaken(email);
LOG.info("Creating new account for username: '{}'.", account.getUsername());
account.setPassword(encodePassword(password));
account = repository.save(account);
return account;
}
Object of an Account class is created automatically from the request.
The full exception stacktrace can be found here.
The problem is caused by
#Id
#Column(name = "ROLE_NAME")
#Enumerated(STRING)
The JPA spec says :
2.1.4 Primary Keys and Entity Identity
The primary key (or field or property of a composite primary key) should be one of the following types: any Java primitive type; any primitive wrapper type; java.lang.String; java.util.Date; java.sql.Date. In general, however, approximate numeric types (e.g., floating point types) should never be used in primary keys. Entities whose primary keys use types other than these will not be portable.
So the combination of #Id and #Enumeration seems not allowed as you are using an enum as id.
With your current code you can write only as many roles as the enum RoleName has elements. These are the predifined roles of your system.
You should do that once on startup (or otherwise make sure they exist)
Add the annotation
#Column(insertable=false, updatable=false)
to Account.roles, this tells the JPA provider, to not insert or update the roles, when an Account is inserted or updated.
This should work.
A User can have multiple Authorities. By default, when I create a new User I want to add him ROLE_USER authority.
This is the User entity:
#Entity
#Table(name = "Users")
public class User
{
#Id
private String username;
private String password;
private String email;
#OneToMany
#JoinTable(
name = "UserAuthority",
joinColumns = #JoinColumn(name = "username"),
inverseJoinColumns = #JoinColumn(name = "authority"))
private List<Authority> authorities = new ArrayList<>();
and Authority entity:
#Entity
public class Authority
{
#Id
private String authority;
I am using basic UserRepository and AuthorityReposiroty both extending CrudRepository.
This is method for registering new user:
public String register(User user)
{
if (userRepository.findOne(user.getUsername()) != null)
{
return "User with given username already exists!";
}
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.getAuthorities().add(authRepository.findOne("ROLE_USER"));
userRepository.save(user);
return "User successfully registered!";
}
When I try to register new user it fails with this error:
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "uk_c1gaghspdr9qx2yt45moye10y"
Detail: Key (authority)=(ROLE_USER) already exists.
I can see in logs that Hibernate is trying to insert ROLE_USER authority which is not possible because it already exists, but I do not understand why. I haven't set cascade property on #OneToManyannotation.
How to I achieve what I want?
This is my configuration:
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtime('org.postgresql:postgresql:9.4-1206-jdbc42')
Thanks in advance!
Problem is in relations. You have OneToMany - means that on other site is relation ManyToOne... If you used some Authority in one User you can't use this entity (object) for other user.
The solution of your issue is changing relation to #ManyToMany insted #OneToMany in User class. It's little streng, but in fact it is relation ManyToMany, because User can has many authorities and any athority can be used in many users.
I'm trying out Spring Boot (latest version, using Hibernate 4.3.7) and I have a problem with my User entity. Here it is (most important part of it):
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
#ElementCollection
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
I am also using Spring Boot JPA repositories to save my entities:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
The problem is that when I add some Roles to roles set, Hibernate won't save it. It will create reference table, but it will only insert data to User table.
I tried to work this problem out, so I created pure Java + Hibernate project and copied my User class into it. Guess what? It worked!
Fun fact is that when I use pure Hibernate on my second project, created roles table looks different that the one created in my Spring Boot project.
On my clean Hibernate project I have table like:
User_roles:
User_Id bigInt(20)
roles int(11)
While using Spring JPA, I got
user_roles (notice lower case)
User (no "_id" part)
roles
What's going on? What I am doing wrong? Is it related to Spring Boot configuration? Thanks.
The following should match your existing tables.
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection
#CollectionTable(name = "User_roles", joinColumns = #JoinColumn(name = "User_Id")
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
My Solution:
Role.java
public enum Role {
USER, ADMIN
}
User.java
#Entity
#Table("usr")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "id"))
#Column(name = "roles", nullable = false)
#Enumerated(EnumType.STRING)
private Set roles;
(rest of properties, getters and setters etc)
}