After first starting the api, the first 3 user creation Post requests fail (screenshot at bottom of post) with the below error (unique constraint vilocation).
Subsequent requests work, with the first created user having an id of 4, then 5, etc...
How can I make the user creation work on the first (3) tries?
I suspect this relates to the pre-seeding of my users, which I'm doing with the below script. Possibly the auto ID generation first tries 1,2,3 -- which are already in use?
INSERT INTO user
VALUES (1, 'user1', 'pass1', 'ADMIN');
INSERT INTO user
VALUES (2, 'user2', 'pass2', 'USER');
INSERT INTO user
VALUES (3, 'user3', 'pass3', 'ADMIN')
could not execute statement; SQL [n/a]; constraint [\"PRIMARY KEY ON
PUBLIC.USER(ID)\"; SQL statement:\ninsert into user (name, password,
role, id) values (?, ?, ?, ?) [23505-196]]; nested exception is
org.hibernate.exception.ConstraintViolationException: could not
execute statement",
#RestController
public class UserResource {
#Autowired
private UserRepository userRepository;
#GetMapping("/users")
public List<User> retrievaAllUsers() {
return userRepository.findAll();
}
#DeleteMapping("/users/{id}")
public void deleteUser(#PathVariable Long id) {
userRepository.deleteById(id);
}
#PostMapping("/users")
public ResponseEntity<Object> createUser(#RequestBody User user) {
User savedUser = userRepository.save(user);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
}
-
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue
private Long id;
private String name;
private String password;
#Enumerated(EnumType.STRING)
private Role role;
public User() {
super();
}
public User(Long id, String name, String password, Role role) {
this.id = id;
this.name = name;
this.password = password;
this.role = role;
}
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 String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
edit - added role class
public enum Role {
USER, ADMIN
}
If you are using AUTO_INCREMENT in the column definition, then try changing strategy from GenerationType.AUTO to GenerationType.IDENTITY.
I noticed a similar behavior when I upgraded a project from Spring Boot 1.5 to 2.0.
Just an assumption. Firstly you are insterting datas with sql , but in your code you are creating new user and saving to db. So this new creation gives id as 1. But your db has a user record which primary key is as 1. Please remove all values from db and create your records from rest controller.
In my opinion, use a sequnce like this ,dont forget to create sequence in db;
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
#SequenceGenerator(name="user_generator", sequenceName = "user_seq", allocationSize=50)
Or read this and choose for your problem to solve.
AUTO: Hibernate selects the generation strategy based on the used
dialect,
IDENTITY: Hibernate relies on an auto-incremented database column to
generate the primary key,
SEQUENCE: Hibernate requests the primary key value from a database
sequence,
TABLE: Hibernate uses a database table to simulate a sequence.
PS: Identity should be more relevant but try others.
you should provide the column names also to make sure it's ordered.
INSERT INTO user (id, name, password, role)
VALUES (1, 'user1', 'pass1', 'ADMIN');
INSERT INTO user (id, name, password, role)
VALUES (2, 'user2', 'pass2', 'USER');
INSERT INTO user (id, name, password, role)
VALUES (3, 'user3', 'pass3', 'ADMIN')
Related
So basically I try to get some data of a User by its ID and it has a field with #OneToOne relationship(bidirectional)
When I try to create a query in JpaRepository
with #Query annotation
#Query("SELECT new com.project.model.user(u.id, u.username, u.profile.last_online, u.profile.about) FROM User u WHERE id = :id)
and a constructor
User(long id, long username, Profile profile) {
this.id = id;
this.username = username;
this.profile = profile;
}
it still can't really find the constructor and says that there is no "matching constructor", is there a way to create the constructor better?
The constructor you expect in your query has (long, String, Timestamp, String). And your constructor takes (long, long, Profile).
So you need to create a matching constructor:
public User(long id, String username, Timestamp lastOnline, String about) {
this.id = id;
this.username = username;
this.profile = new Profile(lastOnline, about); // create new Profile with lastOnline & about
}
This should work.
But may I ask why you're not using the JpaRepository#findById(Long id) method?
I'm trying to post the following json file into mysql database in postman.
{
"rem_month": 3,
"rem_day": 23,
"description": "Happy birthday!",
"username": "mortykrox93"
}
But i keep getting the error "Column 'username' cannot be null"
The app is supposed to allow me to login and add multiple reminders for each user.
Here is the sql files the entities are supposed to model:
user.sql
USE `login-reminder`;
CREATE TABLE `user` (
`email_id` varchar(30) DEFAULT NULL,
`password` varchar(30) DEFAULT NULL,
`username` varchar(30) NOT NULL,
PRIMARY KEY(`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
reminder.sql
USE `login-reminder`;
CREATE TABLE `reminder` (
`rem_num` int(12) NOT NULL,
`rem_month` int(2) DEFAULT NULL,
`rem_day` int(2) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`username` varchar(30) NOT NULL,
PRIMARY KEY(`rem_num`),
FOREIGN KEY(`username`) REFERENCES user(`username`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
Here are the two entity files:
User.java
#Entity
#Table(name="user")
public class User {
#Column(name="email_id")
private String emailId;
#Column(name="password")
private String password;
#Id
#Column(name="username")
private String username;
#OneToMany(mappedBy="theUser", cascade = CascadeType.ALL)
private List<Reminder> reminders;
public User() {
}
public User(String emailId, String password, String username) {
this.emailId = emailId;
this.password = password;
this.username = username;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Reminder.java
#Entity
#Table(name="reminder")
public class Reminder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="rem_num")
private int remNum;
#Column(name="rem_month")
private int remMonth;
#Column(name="rem_day")
private int remDay;
#Column(name="description")
private String description;
#ManyToOne
#JoinColumn(name="username")
private User theUser;
public Reminder() {
}
public Reminder(int remNum, int remMonth, int remDay, String description) {
this.remMonth = remMonth;
this.remDay = remDay;
this.description = description;
}
public int getRemNum() {
return remNum;
}
public void setRemNum(int remNum) {
this.remNum = remNum;
}
public int getRemMonth() {
return remMonth;
}
public void setRemMonth(int remMonth) {
this.remMonth = remMonth;
}
public int getRemDay() {
return remDay;
}
public void setRemDay(int remDay) {
this.remDay = remDay;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Here is the restcontroller.
ReminderController.java
#RestController
public class ReminderController {
#Autowired
private ReminderRepository reminderRepository;
#GetMapping("/reminders")
public List<Reminder> getAllReminders() {
return reminderRepository.findAll();
}
#PostMapping("/reminders")
#CrossOrigin(origins = "http://localhost:4200")
public Reminder createReminder(#RequestBody Reminder reminder) {
return reminderRepository.save(reminder);
}
}
If anyone can help I would appreciate it. Not sure if my entities are matching up with my sql statements, any suggestions would help.
Check your reminder sql, it's username field is set as not null.
The binding is with the User object. So, in your json, it you should send the user like this:
{
"rem_month": 3,
"rem_day": 23,
"description": "Happy birthday!",
"theUser":{
"username":"mortykrox93",
//and other fields if necessary
}
}
This is occurring because of there is no field named username present in Reminder entity class and you are referring to same class in controller with annotation #requestbody to be bind with the request. Actually during deserialization no valid mapping is present for json field named username. so by default username is being set as null because of its datatype string.
Note: It's better to use separate model/pojo class for binding the request. And then map it to proper entity objects.
First, you need change your json that indicates by #user404:
{
"rem_month": 3,
"rem_day": 23,
"description": "Happy birthday!",
"theUser":
{
"username":"mortykrox93",
//and other fields if necessary
}
}
also, the problem is in jackson deserialize, you need to use the anotation for make the relashionship in jackson (is different that Hibernate/JPA) #JsonBackReference and #JsonManagedReference:
In User entity:
#JsonBackReference
#OneToMany(mappedBy="theUser", cascade = CascadeType.ALL)
private List<Reminder> reminders;
In Reminder Entity:
#JsonManagedReference
#ManyToOne
#JoinColumn(name="username")
private User theUser;
Anyway, is recomended use separated DTO classes for transfer data and add in those the jackson annotation. In the entity only use JPA annotations.
I am stuck with a issue of identify which constraint triggers DataIntegrityViolationException. I have two unique constraints: username and email but I have no luck trying to figure it out.
I have tried to get the root cause exception but I got this message
Unique index or primary key violation: "UK_6DOTKOTT2KJSP8VW4D0M25FB7_INDEX_4 ON PUBLIC.USERS(EMAIL) VALUES ('copeland#yahoo.com', 21)"; SQL statement:
insert into users (id, created_at, updated_at, country, email, last_name, name, password, phone, sex, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-193]
Reading the error I know email constraint triggers the validation but I want to return to the user something like:
{type: ERROR, message: "The email already exist"}
I have read in other post and people handle it looking for a constraint name into the exception(eg, users_unique_username_idx) and display a proper message to the user. But I couldn't get that type of constraint name
Maybe I am missing a configuration. I am using:
Spring Boot 1.5.1.RELEASE, JPA, Hibernate and H2
My application.properties
spring.jpa.generate-ddl=true
User.class:
#Entity(name = "users")
public class User extends BaseEntity {
private static final Logger LOGGER = LoggerFactory.getLogger(User.class);
public enum Sex { MALE, FEMALE }
#Id
#GeneratedValue
private Long id;
#Column(name = "name", length = 100)
#NotNull(message = "error.name.notnull")
private String name;
#Column(name = "lastName", length = 100)
#NotNull(message = "error.lastName.notnull")
private String lastName;
#Column(name = "email", unique = true, length = 100)
#NotNull(message = "error.email.notnull")
private String email;
#Column(name = "username", unique = true, length = 100)
#NotNull(message = "error.username.notnull")
private String username;
#Column(name = "password", length = 100)
#NotNull(message = "error.password.notnull")
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#Enumerated(EnumType.STRING)
private Sex sex;
#Column(name = "phone", length = 50)
private String phone;
#Column(name = "country", length = 100)
#NotNull(message = "error.country.notnull")
private String country;
public User() {}
// Getters and setters
}
ControllerValidationHandler.class
#ControllerAdvice
public class ControllerValidationHandler {
private final Logger LOGGER = LoggerFactory.getLogger(ControllerValidationHandler.class);
#Autowired
private MessageSource msgSource;
private static Map<String, String> constraintCodeMap = new HashMap<String, String>() {
{
put("users_unique_username_idx", "exception.users.duplicate_username");
put("users_unique_email_idx", "exception.users.duplicate_email");
}
};
// This solution I see in another stackoverflow answer but not work
// for me. This is the closest solution to solve my problem that I found
#ResponseStatus(value = HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseBody
public ErrorInfo conflict(HttpServletRequest req, DataIntegrityViolationException e) {
String rootMsg = ValidationUtil.getRootCause(e).getMessage();
LOGGER.info("rootMessage" + rootMsg);
if (rootMsg != null) {
Optional<Map.Entry<String, String>> entry = constraintCodeMap.entrySet().stream()
.filter((it) -> rootMsg.contains(it.getKey()))
.findAny();
LOGGER.info("Has entries: " + entry.isPresent()); // false
if (entry.isPresent()) {
LOGGER.info("Value: " + entry.get().getValue());
e=new DataIntegrityViolationException(
msgSource.getMessage(entry.get().getValue(), null, LocaleContextHolder.getLocale()));
}
}
return new ErrorInfo(req, e);
}
The response at this moment is:
{"timestamp":1488063801557,"status":500,"error":"Internal Server Error","exception":"org.springframework.dao.DataIntegrityViolationException","message":"could not execute statement; SQL [n/a]; constraint [\"UK_6DOTKOTT2KJSP8VW4D0M25FB7_INDEX_4 ON PUBLIC.USERS(EMAIL) VALUES ('copeland#yahoo.com', 21)\"; SQL statement:\ninsert into users (id, created_at, updated_at, country, email, last_name, name, password, phone, sex, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [23505-193]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement","path":"/users"}
UPDATE
This is my service layer that handle my persistence operations
MysqlService.class
#Service
#Qualifier("mysql")
class MysqlUserService implements UserService {
private UserRepository userRepository;
#Autowired
public MysqlUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public List<User> findAll() {
return userRepository.findAll();
}
#Override
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
#Override
public User findOne(Long id) {
return userRepository.findOne(id);
}
#Override
public User store(User user) {
return userRepository.save(user);
}
#Override
public User update(User usr) {
User user = this.validateUser(usr);
return userRepository.save(user);
}
#Override
public void destroy(Long id) {
this.validateUser(id);
userRepository.delete(id);
}
private User validateUser(User usr) {
return validateUser(usr.getId());
}
/**
* Validate that an user exists
*
* #param id of the user
* #return an existing User
*/
private User validateUser(Long id) {
User user = userRepository.findOne(id);
if (user == null) {
throw new UserNotFoundException();
}
return user;
}
}
Update #2
Repo to reproduce the issue https://github.com/LTroya/boot-users. I commented my handler on ValidationExceptionHandler.class in order to see the exception.
Send twice json at Json to test on Readme.md to POST /users/
What you want to do is rather than specify the unique column requirement on the #Column annotation, you can actual define those with names on the #Table annotation that JPA provides to have further control of those constraints.
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "UC_email", columnNames = { "email" } ),
#UniqueConstraint(name = "UC_username", columnNames = " { "userName" } )
})
There are now two ways for handling the exception:
In the controller
You could elect to place the parsing logic in your controller and simply catch the DataIntegrityException that spring throws and parse it there. Something like the following pseudo code:
public ResponseBody myFancyControllerMethod(...) {
try {
final User user = userService.myFactoryServiceMethod(...);
}
catch ( DataIntegrityException e ) {
// handle exception parsing & setting the appropriate error here
}
}
The ultimate crux with this approach for me is we've moved code to handle persistence problems up two layers rather than the layer immediately above the persistence tier. This means should we have multiple controllers which need to handle this scenario we either find ourselves doing one of the following
Introduce some abstract base controller to place the logic.
Introduce some helper class with static methods we call for reuse.
Cut-n-paste the code - Yes this happens more than we think.
Placing the code in the presentation tier also introduces concerns when you need to share that service with other consumer types that may not be actually returning some type of html view.
This is why I recommend pushing the logic down 1 more level.
In the service
This is a cleaner approach because we push the validation of the constraint handling to the layer above the persistence layer, which is meant to ultimately be where we handle persistence failures. Not only that, our code actually documents the failure conditions and we can elect either to ignore or handle them based on context.
The caveat here is that I'd recommend you create specific exception classes that you throw from your service tier code in order to identify the unique constraint failures and throw those after you have parsed the ConstraintViolationException from Hibernate.
In your web controller, rest controller, or whatever other consumer that is calling into your service, you simply need to catch the appropriate exception class if necessary and branch accordingly. Here's some service pseudo code:
public User myFancyServiceMethod(...) {
try {
// do your stuff here
return userRepository.save( user );
}
catch( ConstraintViolationException e ) {
if ( isExceptionUniqueConstraintFor( "UC_email" ) ) {
throw new EmailAddressAlreadyExistsException();
}
else if ( isExceptionUniqueConstraintFor( "UC_username" ) ) {
throw new UserNameAlreadyExistsException();
}
}
}
You can specify unique constraints separately but you'd need to that on the entity level like
#Entity(name = "users")
#Table(name = "users", uniqueConstraints = {
#UniqueConstraint(name = "users_unique_username_idx", columnNames = "username"),
#UniqueConstraint(name = "users_unique_email_idx", columnNames = "email")
})
public class User extends BaseEntity { ... }
I have an issue with loading data into an in-memory database on application initialization. I have created schema.sql and data.sql files containing table structure and initial data.
schema.sql :
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(64) NOT NULL,
password VARCHAR(64)
);
and data.sql :
INSERT INTO users (id, username, password) VALUES
(1, 'usr1', 'bigSecret'),
(2, 'usr2', 'topSecret');
I am using JpaRepository for working with data layer:
public interface UserRepository extends JpaRepository<User, Long> {
}
And I also configure application.properties
spring.datasource.initialize=true
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=- 1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
But when I call
List<User> users = userRepository.findAll();
User entity
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
private Long id;
private String username;
private String password;
public User() { }
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = 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;
}
}
I get an empty list, but I should get two pre-populated users from my in-memory H2 database. What's wrong with in memory database?
Thanks.
You can always try to run those scripts per specification of h2, where you should add an INIT script in your connection url (being one of the options):
jdbc:h2:mem:test;INIT=RUNSCRIPT FROM '~/schema.sql'\;RUNSCRIPT FROM '~/data.sql'"
This functionality is enabled via the INIT property. Note that
multiple commands may be passed to INIT, but the semicolon delimiter
must be escaped, as in the example below.
Update
Be aware that having these options in your application.properties:
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=true
spring.datasource.initialize=true
may cause some clashing during startup. So you should always aim for one or the other, but never both at the same time.
For simple cases just these alone are sufficient to auto build tables and reload after shutdown & startup
I solved a similar problem when I added the following lines to the application.properties:
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.session.jdbc.initialize-schema=always
spring.sql.init.data-locations=classpath:schema.sql,classpath:data.sql
I suppose it is not standard way of doing that so any tips will be helpful, here is my code:
#RequestMapping("/register")
public String register(Map<String, Object> map, #ModelAttribute("user") MyUser user) {
if(user.getLogin() == ""){
map.put("user", new MyUser());
}
else{
map.put("user", user);
map.put("result", userService.addMyUser(user));
}
return "register";
}
what cause following error:
org.hibernate.AssertionFailure: null id in org.mypackage.MyUser entry
(don't flush the Session after an exception occurs)
Here is MyUser class:
#Entity
#Table(name="MyUser")
public class MyUser{
#Id
#Column(name="idMyUser")
#GeneratedValue
private Integer id;
#Column(name="login")
private String login;
#Column(name="password")
private String password;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Try changing the strategy and/or generator for the #GeneratedValue, see here and here for details (for example, you could try #GeneratedValue(strategy = GenerationType.IDENTITY). You could also check if your database table is set to generate the primary key values. The exception seems to indicate that the primary key -field is left unset by the current strategy and/or generator.