I am developing a spring boot project, linked to a MySQL database through Hibernate.
Everything runs fine when the database starts empty. However, when the dummy data data.sql file runs before lauching the application, and then inserting objects through the application, the insertion doesn't take into account previous IDs, therefore resulting in duplicate entries until the number of lines is reached :
I'll try explaining better with an example :
At application start, the data.sql file inserts 3 dummy users.
Reaching registration page to add a new user and submitting, Hibernate returns an error 'Duplicate entry '1' for PRIMARY key', then 'Duplicate entry '2' for PRIMARY key' and 'Duplicate entry '3' for PRIMARY key' after retrying two times.
At the fourth retry, the user is added.
The ID therefore does auto-increment, but doesn't take into account previously inserted rows through data.sql.
Note that I've tried playing around with the generation type (AUTO / IDENTITY) of the User class with no success, and my hibernate datasource is on create-drop mode.
Update :
User entity :
#Data
#Builder
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "users")
public class User {
public final static Role DEFAULT_ROLE = new Role();
static {
DEFAULT_ROLE.setId(2);
DEFAULT_ROLE.setRole("NORMAL");
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#Column(name = "user_name")
#Length(min = 5, message = "*Your user name must have at least 5 characters")
#NotEmpty(message = "*Please provide a user name")
private String userName;
#Column(name = "email")
#Email(message = "*Please provide a valid Email")
#NotEmpty(message = "*Please provide an email")
private String email;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#NotEmpty(message = "*Please provide your password")
private String password;
#Column(name = "name")
#NotEmpty(message = "*Please provide your name")
private String name;
#Column(name = "last_name")
#NotEmpty(message = "*Please provide your last name")
private String lastName;
#Column(name = "active")
private Boolean active;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column(name = "phone_number")
#NotEmpty(message = "*Please provide a phone number")
private String phoneNumber;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "address_id", referencedColumnName = "address_id")
private Address address;
}
Any idea what would solve it? :)
Cheers.
The problem is not in Hibernate, but in the statements in data.sql.
In those INSERT statements your probably give explicit values for the user_id column, hence MySQL does not increment its AUTO_INCREMENT counter. Hibernate's default generation strategy for MySQL is IDENTITY, which relies on the AUTO_INCREMENT feature.
The easiest solution is to omit the users_id column in the INSERT statements. You can however also set the initial AUTO_INCREMENT value for the table to a higher number:
ALTER TABLE users AUTO_INCREMENT=1001
Edit: Your identity field is declared as int id, therefore it can never be null. Change it to Integer, so that Hibernate can distinguish between persisted (id != null) and not persisted entities.
Related
I have an entity in containing :
#Entity
#Table(name = "pictures")
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
public class PictureEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", insertable = false, updatable = false, nullable = false)
private UUID id;
#Column
private String path;
#Column(name = "thumb_path")
private String thumbPath;
#Column
#Enumerated(EnumType.STRING)
private Status status;
#Column(name = "creation_utc")
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimeUtc;
#Column(name = "creation_local")
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimeLocal;
#ManyToOne
#JoinColumn(name = "project_id", updatable = true, insertable = true)
private ProjectEntity project;
#ManyToOne
#JoinColumn(name = "user_id", updatable = true, insertable = true)
private UserEntity user;
#OneToOne(mappedBy = "picture", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private ProcessedPictureEntity processedPicture;
public enum Status {
VALIDATED,
PROCESSED,
REJECTED,
WAITING_VALIDATION
}
}
When I call a save with H2 database, it saves the "project_id" field too.
But if I use mysql, the generated query isn't the same, project is not saved (which I think is the correct behavior).
I want the test with H2 to crash if updatable/insertable on project_id are false.
How can I correct this ?
If I understand you correctly you have two problems:
H2 and MySQL behave differently causing bugs to slip through your tests.
You want to test if a certain field got updated.
For 1.: I recommend Testcontainers. It allows you to run tests with an actual MySQL database (or any other database that you can get a docker image for).
This will make your integration tests way more valuable.
For 2.: Execute whatever code you suspect does the update in question and then check if the field got changed.
Make sure the changes get flushed which is a common cause of tests not behaving as on things.
For checking for changes I recommend Springs JdbcTemplate for easily executing queries.
Say I have a class that holds user info
User.java
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
//User Data
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
}
In front end page admin can update Bio Dynamically by defining new field. Say clicking on + button he can add new field called middle name, age or address, etc.
P.S. It is kind of admin privilege and number of updates runtime will be limited and hence no issue of creating unlimited fields.
How can I address this dynamic addition of entity in MySQL using Spring Boot?
You might add new fields using a custom map, for example:
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "custom_fields")
#MapKeyColumn(name = "field")
private Map<String, String> customFields;
Why is this mapping unable to create the column id as autoincrement?
#Entity(name = "user_role")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#OneToOne(cascade = CascadeType.MERGE, targetEntity = Role.class)
#JoinColumn(name = "role_id")
private Role role;
#OneToOne(cascade = CascadeType.MERGE, targetEntity = User.class)
#JoinColumn(name = "user_id")
private User user;
}
Thanks in advance!
To use a AUTO_INCREMENT column in your MySQL, you are supposed to use an IDENTITY strategy:
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", nullable = false, unique = true)
private Long id;
//Your code
To learn more check this Link
It is clearly mentioned that
The IDENTITY strategy also generates an automatic value during commit for every new entity object.
Well, just to help people.
The problem was the name of my entity "user_role", I don't know why but after changing the entity name to "userrole" everything worked and the id column was created as AUTOINCREMENT.
Hope this helps!
I work with a Spring boot application and this is the entity class I work,
#Entity
public class User {
// form:hidden - hidden value
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// form:input - textbox
#Column(name = "name", columnDefinition = "VARCHAR(30)", nullable = false)
String name;
// form:input - textbox
#Column(name = "email", columnDefinition = "VARCHAR(50)", nullable = false)
String email;
// form:input - password
#Column(name = "password", columnDefinition = "VARCHAR(20)", nullable = false)
String password;
// form:textarea - textarea
#Column(name = "address", columnDefinition = "VARCHAR(255)")
String address;
// form:input - password
String confirmPassword;
// form:checkbox - single checkbox
#Column(name = "newsletter")
boolean newsletter;
// form:radiobutton - radio button
#Column(name = "sex", columnDefinition = "VARCHAR(1)")
String sex;
// form:radiobuttons - radio button
#Column(name = "number")
Integer number;
// form:select - form:option - dropdown - single select
#Column(name = "country", columnDefinition = "VARCHAR(10)")
String country;
// form:checkboxes - multiple checkboxes
#ElementCollection
#NotNull
List<String> framework;
// form:select - multiple=true - dropdown - multiple select
#ElementCollection
List<String> skill;
}
I intend to only create the following columns in the user table,
id,
name,
email,
address,
password,
newsletter,
framework,
sex,
Number,
Country,
Skill
For example, the corresponding SQL will be like (though I don't want to write explicitly),
CREATE TABLE user (
id LONG GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
name VARCHAR(30),
email VARCHAR(50),
address VARCHAR(255),
password VARCHAR(20),
newsletter BOOLEAN,
framework VARCHAR(500),
sex VARCHAR(1),
Number INTEGER,
Country VARCHAR(10),
Skill VARCHAR(500)
);
The starting controller class,
#Controller
public class UserController {
private final Logger logger = LoggerFactory.getLogger(UserController.class);
#Autowired
private UserService userService;
private static List<User> populateDefaultUserValues() {
List<User> users = new ArrayList<>();
User user = new User();
user.setName("Ella");
user.setEmail("xyz#gmail.com");
user.setPassword("df32d343H");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
user = new User();
user.setName("Alex");
user.setEmail("alex#hotmail.com");
user.setPassword("12HH2d343H");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
user = new User();
user.setName("Romanna");
user.setEmail("romanna#hotmail.com");
user.setPassword("Rommann343");
user.setFramework(Arrays.asList("Spring MVC, GWT".split("\\s*,\\s*")));
users.add(user);
return users;
}
#GetMapping(value = "/")
public String index() {
return "redirect:/users";
}
#GetMapping(value = "/users")
public String showAllUsers(Model model) {
List<User> users = populateDefaultUserValues();
users.forEach(user -> {
userService.save(user);
});
model.addAttribute("users", userService.findAll());
return "list";
}
}
When I run the app, I see that this created in the MYSQL database,
How do I omit the confirmed_password field and add the columns of skill and framework? I expect them to be VARCHAR?
How do I omit the confirmed_password field
You should use the #Transient annotation.
add the columns of skill and framework?
Since you've marked them as #ElementCollection - there will be no columns in user table, as you can't persist a collection of values in a single relation attribute (it would violate 1NF restrictions). There are two additional tables, named like user_framework and user_skill, they are related to user table with user_id columns.
You may read the Java Persistense wiki and the Hibernate documentation about collections to find different examples of proper usage. Although, your code is fine and, in common case, you should not worry about database tables, relations and data consistency - ORM framework (Hibernate, by default) will handle all these things for you.
I am using Spring MVC, Hibernate to store 2 entities - FacilityMember and User. Business requirement is such that once entry is inserted into 'FacilityMember' then 'User' entry should get inserted with his email id as user name.
I have unidirectional mapping from FacilityMember to User. I need User->id for some further action inside my service layer. After hibernate persists I do get ID for FacilityMember but I get 0 for FacilityMember->User->Id.
Code snippet as follows:
My FacilityMember
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
//other attributes
#OneToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Cascade(CascadeType.SAVE_UPDATE)
#JoinColumn(name = "userId")
private User user;
//getters setters
My User entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 4, max = 30)
private String username;
//getters setters
My service layer is having method level transaction which calls below Dao layer code.
User user = new User();
user.setUsername(userName);
facilityMember.setUser(user);
persist(facilityMember);
return facilityMember;
Inside service layer my facilityMember->id is having proper value but
facilityMember->User->id is 0. Data gets stored properly into mysql tables.
Hibernate queries are executed in following manner
Insert FacilityMember ...
Insert User ..
Update FacilityMember ..
What's going wrong - how to get newly inserted id of my mapped entity ?
Thanks in advance
Manisha