I am trying to implement DAOs to work with Spring Security database authentication in Hibernate/JPA2. Spring uses following relations and associations in order to represent user & roles:
repesented as postgresql create query:
CREATE TABLE users
(
username character varying(50) NOT NULL,
"password" character varying(50) NOT NULL,
enabled boolean NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (username)
);
CREATE TABLE authorities
(
username character varying(50) NOT NULL,
authority character varying(50) NOT NULL,
CONSTRAINT fk_authorities_users FOREIGN KEY (username)
REFERENCES users (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
);
Using the on-board implementations of GrantedAuthorities, UserDetailsService and UserDetailsmanager, everything is fine. However, I am not satisfied with the JDBC implementation of Spring and would like to write my own ones. In order to do so, I tried to create a representation of the relations by following business objects:
The user entity:
#Entity
#Table(name = "users", uniqueConstraints = {#UniqueConstraint(columnNames = {"username"})})
public class AppUser implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = -8275492272371421013L;
#Id
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "password", nullable = false)
#NotNull
private String password;
#OneToMany(
fetch = FetchType.EAGER, cascade = CascadeType.ALL,
mappedBy = "appUser"
)
private Set<AppAuthority> appAuthorities;
#Column(name = "accountNonExpired")
private Boolean accountNonExpired;
#Column(name = "accountNonLocked")
private Boolean accountNonLocked;
#Column(name = "credentialsNonExpired")
private Boolean credentialsNonExpired;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "personalinformation_fk", nullable = true)
#JsonIgnore
private PersonalInformation personalInformation;
#Column(name = "enabled", nullable = false)
#NotNull
private Boolean enabled;
public AppUser(
String username,
String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<? extends AppAuthority> authorities,
PersonalInformation personalInformation
) {
if (((username == null) || "".equals(username)) || (password == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.appAuthorities = Collections.unmodifiableSet(sortAuthorities(authorities));
this.personalInformation = personalInformation;
}
public AppUser() {
}
#JsonIgnore
public PersonalInformation getPersonalInformation() {
return personalInformation;
}
#JsonIgnore
public void setPersonalInformation(PersonalInformation personalInformation) {
this.personalInformation = personalInformation;
}
// Getters, setters 'n other stuff
And the authority entity as an implementation of GrantedAuthorities:
#Entity
#Table(name = "authorities", uniqueConstraints = {#UniqueConstraint(columnNames = {"id"})})
public class AppAuthority implements GrantedAuthority, Serializable {
//~ Instance fields ================================================================================================
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "authority", nullable = false)
private String authority;
// Here comes the buggy attribute. It is supposed to repesent the
// association username<->username, but I just don't know how to
// implement it
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "appuser_fk")
private AppUser appUser;
//~ Constructors ===================================================================================================
public AppAuthority(String username, String authority) {
Assert.hasText(authority,
"A granted authority textual representation is required");
this.username = username;
this.authority = authority;
}
public AppAuthority() {
}
// Getters 'n setters 'n other stuff
My problem is the #ManyToOne assoc. of AppAuthorities: It is supposed to be "username", but trying and doing so throws an error, because I've got to typify that attribute as String ... while Hibernate expects the associated entity. So what I tryied is actually providing the correct entity and creating the association by #JoinColumn(name = "appuser_fk"). This is, of course, rubbish, because in order to load the User, I will have the foreign key in username, while Hibernate searches for it in appuser_fk, which will always be empty.
So here is my question: any suggestion on how to modify the above metioned code in order to get a correct JPA2 implementation of the data model?
Thanks
You AppAuthority doesn't need username at all. Spring Security can't depend on it because it depends on the GrantedAuthority interface which doesn't have any methods to access username.
But the better practice is to decouple your domain model from Spring Security. When you have a custom UserDetailsService, you don't need to mimic neither Spring Security's default database schema nor its object model. Your UserDetailsService can load your own AppUser and AppAuthority and then create UserDetails and GrantedAuthoritys based on them. This leads to cleaner design with better separation of concerns.
This looks like the classic Hibernate problem of using a domain-specific key. A possible fix would be to create a new primary key field; e.g. userId int for the Users and Authorities entities / tables, remove Authorities.userName, and change Users.userName to a unique secondary key.
There is one more way that decouples the UserDetailsService from JPA/Hibernate.
You can model your User and Authority class as you like and use this while defining userDetailsService in configuration:-
<sec:jdbc-user-service data-source-ref="webDS"
id="userDetailsService"
users-by-username-query="SELECT USER_NAME,PASSWORD,TRUE FROM CUSTOMER WHERE USER_NAME=?"
authorities-by-username-query="SELECT c.USER_NAME,r.ROLE_NAME from CUSTOMER c
JOIN CUSTOMER_ROLE cr ON c.CUSTOMER_ID = cr.CUSTOMER_ID
JOIN ROLE r ON cr.ROLE_ID = r.ROLE_ID
WHERE USER_NAME=?" />
This way you can define fine tuned SQL query to fetch user and roles from your database.
All you need to take care of is the table and column name.
Related
I have a little problem to understand how it make sense to insert a List of languages in JPA for a Oracle 19 DB.
The API receives a DTO with a list of chosen languages for one user.
{
"userId": 173125,
"logname": "testor2",
"lastname": "Testimator2",
"firstname": "Testor2",
"selectedLang": [
"German",
"English"
],
}
The Table looks like this for User and had a Table with pk and fk for user and language code.
The Table should remove unused languages that not in the received DTO || insert new languages in the table that not exist.
Tables that used in the db are:
USER
Columnname
Type
keys
USERID
int
PK
LOGNAME
row
-
NAME
varchar
-
LASTNAME
varchar
-
USERLANGUAGE
Columnname
Type
keys
USERLANGUAGEID
int
PK --part of primary key
USERID
int
PK --part of primary key
Languagecode
varchar
-
Now the tricky part. I want to store the languages as a List and have thought that jpa deals with the update, remove or inserts itself.
#Entity
#Table(name = "user", schema = "test")
public class UserEntity {
#Id
#Column(name = "USERID", nullable = false)
private long userid;
#Column(name = "LOGNAME", nullable = false, length = 50)
private String logname;
#Column(name = "NAME", nullable = false, length = 60)
private String name;
#Column(name = "LASTNAME", nullable = false, length = 80)
private String lastname;
#OneToMany(mappedBy = "userid", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserlanguageEntity> languages;
public List<UserlanguageEntity> getLanguages() {
return languages;
}
public void setLanguages(List<UserlanguageEntity> languages) {
this.languages = languages;
}
public UserEntity addLanguage(UserlanguageEntity userlanguageEntity) {
languages.add(userlanguageEntity);
userlanguageEntity.setUserid(this);
return this;
}
public UserEntity removeLanguage(UserlanguageEntity userlanguageEntity) {
languages.remove(userlanguageEntity);
userlanguageEntity.setUserid(null);
return this;
}
}
UserLanguageEntity
#Entity
#Table(name = "USRLANGUAGE")
public class UsrlanguageEntity {
#EmbeddedId
private UserId user;
private String languagecode;
#Basic
#Column(name = "LANGUAGECODE", nullable = true, length = 4)
public String getLanguagecode() {
return languagecode;
}
public void setLanguagecode(String languagecode) {
this.languagecode = languagecode;
}
public UserId getUser() {
return user;
}
public void setUser(UserId user) {
this.user = user;
}
...
}
Embedded ID
#Embeddable
public class UserId implements Serializable {
#SequenceGenerator(name = "userlanguageid", sequenceName =
"USERLANGUAGEID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"userlanguageid")
#Column(name = "USERLANGUAGEID", nullable = false, precision = 0)
private long userlanguageid;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USERID")
private UserEntity userid;
...
}
So the fetch method is fine and works as intended, but when i want to update the languages I get errors.
First of all get the list out of db to an UserEntity, then want to clear the UserLanguageEntitylist -> the list in the UserEntity. And than add the received list, maybe thats imposible. So what would be the best implementation to get this to work. First i want to delete all languagecodes in the db and write the new list into it, but it feels a bit dirty to me because maybe some have not to be changed.
public void setUserDataForCompany(UserDto userDto) {
CompanyEntity companyEntity = companyRepository.findById(41).orElseThrow(
NoSuchElementException::new);
UserEntity userEntity = userRepository.findById(userDto.getUserId()).orElseThrow(
NoSuchElementException::new);
companyUserDataMapper.toEntity(userDto, companyEntity, userEntity);
setLanguagesForUser(userDto, userEntity);
userRepository.save(userEntity);
}
// Here i have no clue to update existing or store missing languages
private void setLanguagesForUser(UserDto userDto, UserEntity userEntity) {
userDto.getSelectedLang().forEach(language -> {
UserlanguageEntity userlanguageEntity = new UserlanguageEntity();
userlanguageEntity.setLanguagecode(language.getLngCode());
userEntity.addLanguage(userlanguageEntity);
});
}
Could someone get me a hint to doing it the right way?
Thanks a lot and cheers
Problem I'm trying to solve
I'm trying to model a #ManyToMany relation between a User and Role, such that a user can have n roles, and one role is referenced by several users. A role can be persisted even if it's not referenced by any user (detached), and a user with no roles is allowed too.
The same kind of relation must be built between Role and ResourcePermission.
To give you an idea about how each entity looks like:
Both ResourcePermission and Role have a finite set of values. For example, if Patient happens to be a resource, then one resource permission could be "PATIENT:READ" or "PATIENT:WRITE", and the role DOCTOR has several of these permissions. I hope it's clear sofar how my data model looks like.
What I'm using
Currently, I'm using spring-data-jpa version 2.4.2 to model my entities, and to create my CRUD repos. Except for base path and media type, I don't have any specific configuration (all is set to default).
Hibernate is my persistence provider atm .
Concerning my datasource, I'm using in-memory H2 for my development environment, and again no specific configuration there either.
How I'm solving it
Here's how my entities look like
User.java
#Table
#Entity
#Data
public class User implements Serializable {
private static final long serialVersionUID = 1123146940559321847L;
#Id
#GeneratedValue(generator = "user-id-generator")
#GenericGenerator(name = "user-id-generator",
strategy = "....security.entity.UserIdGenerator",
parameters = #Parameter(name = "prefix", value = "USER-")
)
#Column(unique = true, nullable = false)
private String id;
#Column
private int age;
#Column(unique = true, nullable = false)
private String username;
#Column(unique = false, nullable = false)
private String password;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.MERGE
)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private List<Role> roles = Collections.emptyList();
public User withId(final String id) {
this.id = id;
return this;
}
public User withAge(final int age) {
this.age = age;
return this;
}
public User withUsername(final String username) {
this.username = username;
return this;
}
public User withPassword(final String password) {
this.password = password;
return this;
}
public User withRoles(final Role... roles) {
return withRoles(Arrays.stream(roles).collect(Collectors.toList()));
}
public User withRoles(final List<Role> roles) {
this.roles = roles;
return this;
}
}
Role.java
#Data
#NoArgsConstructor
#Table
#Entity
public class Role implements Serializable {
private static final long serialVersionUID = 812344454009121807L;
#Id
private String roleName;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
#JoinTable(
name = "role_resource_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "resource_permission_id")
)
private Set<ResourcePermission> resourcePermissions = Collections.emptySet();
#ManyToMany(
mappedBy = "roles",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private List<User> users = Collections.emptyList();
public Role(final String roleName) {
setRoleName(roleName);
}
public void setRoleName(final String roleName) {
final RoleType roleType = RoleType.of(roleName);
this.roleName = roleType.getRoleName();
final Set<ResourcePermission> resourcePermissions = roleType.getResourcePermissions().stream()
.map(ResourcePermissionType::getPermissionName)
.map(ResourcePermission::new)
.collect(Collectors.toSet());
setResourcePermissions(resourcePermissions);
}
public void setResourcePermissions(final Set<ResourcePermission> resourcePermissions) {
if (this.resourcePermissions.isEmpty()) {
this.resourcePermissions = resourcePermissions;
}
}
}
ResourcePermission.java
#NoArgsConstructor
#Data
#Table
#Entity
public class ResourcePermission implements Serializable {
private static final long serialVersionUID = 883231454000721867L;
#Id
private String permissionName;
public ResourcePermission(final String permissionName) {
setPermissionName(permissionName);
}
#ManyToMany(
mappedBy = "resourcePermissions",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private Set<Role> roles = Collections.emptySet();
public void setPermissionName(String permissionName) {
final ResourcePermissionType permissionType = ResourcePermissionType.of(permissionName);
this.permissionName = permissionType.getPermissionName();
}
}
RoleType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RoleType {
DOCTOR("DOCTOR", doctorsPermissions()),
TECHNICIAN("TECHNICIAN", technicianPermission()),
ADMIN("ADMIN", adminPermissions());
#Getter
private String roleName;
#Getter
private final List<ResourcePermissionType> resourcePermissions;
public static RoleType of(final String roleName) {
return Arrays.stream(values())
.filter(roleType -> roleType.getRoleName().equals(roleName.toUpperCase()))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static List<ResourcePermissionType> doctorsPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
PATIENT_READ, PATIENT_WRITE
);
}
private static List<ResourcePermissionType> adminPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE,
ORG_UNIT_READ, ORG_UNIT_WRITE
);
}
private static List<ResourcePermissionType> technicianPermission() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE
);
}
}
ResourcePermissoinType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResourcePermissionType implements Serializable {
PATIENT_READ("PATIENT:READ"), PATIENT_WRITE("PATIENT:WRITE"),
ENCOUNTER_READ("ENCOUNTER:READ"), ENCOUNTER_WRITE("ENCOUNTER:WRITE"),
BUILDING_UNIT_READ("BUILDING_UNIT:READ"), BUILDING_UNIT_WRITE("BUILDING_UNIT:WRITE"),
ORG_UNIT_READ("ORG_UNIT:READ"), ORG_UNIT_WRITE("ORG_UNIT:WRITE");
#Getter
private String permissionName;
public static ResourcePermissionType of(final String permissionName) {
return Arrays.stream(values())
.filter(v -> v.getPermissionName().equals((permissionName.toUpperCase())))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Unfortunately, the javax persistence API does not accept enums as entities. I tried using #Embeddable and #IdClass too, but that didn't work out for me either. I was not able to generate the schema that I had in mind. On the other hand, the schema was successfully generated using this model.
At the moment, both the Role repository as well as the Resource Permission repository are not exported (#RepositoryRestResource(..., exported = false)), so in order for you to persist those two entities, you'd have to provide that data in User. Keep that in mind, because that's also a part of the discussion that I want to talk about.
Now let's examine this integration test for the UserCrudRepository that will attempt to add a new user after a successful authentication.
#TestMethodOrder(OrderAnnotation.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class UserCrudRepositoryApiITest {
private final List<User> testUsers = Arrays.asList(
new User().withUsername("dummy_username_01").withPassword("dummy_password_01").withAge(35)
.withRoles(new Role("ADMIN")),
new User().withUsername("dummy_username_02").withPassword("dummy_password_02").withAge(40)
.withRoles(new Role("DOCTOR")),
new User().withUsername("dummy_username_03").withPassword("dummy_password_03").withAge(45)
);
.
.
#Order(1)
#Test
public void afterAuthenticationAddNewUser() throws Exception {
final String generatedToken = login();
// serialize the user
final String requestJson = objectMapper.writeValueAsString(testUsers.get(0));
final RequestBuilder request = MockMvcRequestBuilders.post(USER_CRUD_BASE_URL)
.header(HttpHeaders.AUTHORIZATION, generatedToken)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson);
final String serializedContent = mvc.perform(request)
.andExpect(status().isCreated())
.andReturn()
.getResponse()
.getContentAsString();
final User storedUser = objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(serializedContent, User.class);
assertThat(storedUser).isEqualTo(testUsers.get(0));
}
.
.
}
In here, I'm getting a status code conflict 409, and not able to persist all entities at once.
Unfortunately, SO allows only 30000 character, so please navigate to this repo if you would like to take a look at the log.
My Questions
I couldn't for the life of me understand where that referential integrity constraint violation
is occurring. Any idea?
Any suggestions on how to model these relations in a better way are welcome!
Another problem I'm having with JPA repos is that the only way to persist roles and resource permissions is by providing that data in the user's body. I would like those entities to be managed independently of the user (each with its own separate repository), so I tried exporting their repositories. However, the problem then is that you no longer can pass Role data in the body of a User, but rather A reference to that entity. Is there a way to get the best of both worlds.
I hope I made my problem clear, if not, I'd be happy to elaborate more.
I guess when a User is persisted, it also does the insert for the user_role table, but the role wasn't persisted yet. You could try to persist the Role first or use PERSIST cascading at the User#roles association.
I start to use Spring Security. For now I seting that user must login if they wont create table. Or, for example, in the ControllerClass I configured to only user with role ROLE_USER can delete a table.
My question is, in which way I can set that, when user login and he create some table and create teamPlayers , that the table or players can only edit or delete user who did create the table and the players.
for example , I have in Controller method for delete table ...
#RestController
#RequestMapping(value="/api/tables")
public class ApiTableController {
#Autowired
TableService tableService;
#Autowired
TableConverter tableConverter;
#PreAuthorize("hasRole('ROLE_USER')")
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public ResponseEntity<TableDTO> deleteTable(#PathVariable Long id) {
Table table = tableService.findOne(id);
if (table != null) {
TableDTO tableDTO = tableConverter.table2TableDTO(table);
tableService.remove(id);
return new ResponseEntity<>(tableDTO, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
In this case, all users with role ROLE_USER can delete all table , but I wont to can delete table only user how created table ... Is there any rule how it works or standard code? Like a profil on StackOwerflow . Everyone can see what we write ,Everyone can create profil and only I can edit my profil or my quastions which I wrote on the site. How I can do somthing like that with Spring security?
this is class User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
#NotNull
#Column(name = "user_id")
private Long id;
#Column(name = "username")
private String name;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
#Column(name = "country")
private String country;
#Column(name = "city")
private String city;
#Column(name = "dateCreated")
private Date dateCreated;
#Column(name = "enabled")
private boolean active;
#JoinTable(name = "user_security_role", joinColumns = { #JoinColumn(name = "user_id",
referencedColumnName = "user_id") }, inverseJoinColumns = {
#JoinColumn(name = "security_role_id", referencedColumnName = "id") })
#ManyToMany
private Set<SecurityRoleUser> securityRoleCollection;
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof User)) {
return false;
}
User other = (User) object;
if ((this.id == null && other.id != null)
|| (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
And this is class Table ...
#Entity
#javax.persistence.Table(name="tblTable")
public class Table {
#Id
#GeneratedValue
#Column(name="table_id")
private Long id;
#Column(name="name", nullable=true)
private String name;
#Column(name="sport", nullable=true)
private String sport;
#Column(name="typeTable", nullable=true)
private String TypeTable;
#Column(name="dateCreated", nullable=true)
private Date dateCreated;
#Column(name="changed", nullable=true)
private Date changed;
#Column(name="description", nullable=true)
private String description;
I use hibernate, maven, RESTFull web server, backbone.js....
Not really an detailed answer but allready too long for a comment.
Spring security comes with a feature that is exactly what you need : Domain Object Security or ACLs
It's a rather advanced feature because if needs to add a set of tables to represents the authorizations of users on every secured domain object. One for the object classes, one for the objects themselves (only primary key is stored) and others for actual authorizations. In fact, it can be seen as the authorizations on a shared filesystem.
You normaly use then method security with #PreAuthorize annotation that allows to use an expression containing the actual parameters of the method. You directly allow a user to modify, or delete each and every domain object.
In addition to the Spring Security Reference Manual already cited above, you can find a complete tutorial on ACLs on krams::: Spring Security 3: Full ACL Tutorial.
My advice : try and experiment and ask questions here if you get stuck on some specific problems.
You can use #PreRemove/ #PreUpdate / #PrePersist in your entity and implements you own logic.
#PreRemove
private void preventUnAuthorizedRemove() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
if(!name.equals(this.username)){
throw new NotAuthorizedException("User can only delete himself ");
}
}
I'm currently taking a look into Hibernate (with Spring, if important) and I tried to make a small web application in which someone can register and log in. There is a secured area which is protected by Spring Security and I already got my UserDetailsService working, so Spring Security is using the Hibernate stuff to do the login.
Now I am working on the registration for new users. I thought about how to do it and I came to having a separate table for the activations which would basically have 3 columns: activationId, username and activation_code. The activation code would be some kind of hash but I guess its not relevant for this question.
My question basically is, how to do the relationship between my users table and the activations. I need to link the username from the activations to the username from the users table.
Do I have to use a One To One Mapping? Or how can I link exactly one column from another table into the activation table?
Here's my User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", unique = true, nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 64)
private String password;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<UserRole> userRole = new HashSet<UserRole>(0);
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
public User() {
}
public User(String username, String password, boolean enabled) {
this.username = username;
this.password = password;
this.enabled = enabled;
}
public User(String username, String password, boolean enabled, Set<UserRole> userRole) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.userRole = userRole;
}
// Getters and setters for all attributes omitted
}
If you need the UserRole Entity too, just tell me and I will add it but I think it's not needed. Here is also the current version of my UserActivation Entity, but of course it is not finished yet:
import javax.persistence.*;
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#Column
private String userName; // here we need some magic relationship
}
You can use the ManyToOne mapping :
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#ManyToOne(optional=false)
#JoinColumn(name="USER_NAME")
private User user; // since your User Id is username, the join column will be username
}
if you want the username to be unique, wich means one activation for a single user :
#Entity
#Table(name = "activations",uniqueConstraints=#UniqueConstraint(columnsnames="USER_NAME"))
I have a USER table associated with many other tables, in general, star topology.
Like this:
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "user_USERID_GENERATOR", sequenceName = "user_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userR_USERID_GENERATOR")
#Column(name = "user_id")
private long userId;
#Basic
#Column(name = "password_hex")
private String password;
#Basic
#Column(name = "language")
private String language;
#Temporal(TemporalType.DATE)
private Date created;
#Temporal(TemporalType.DATE)
private Date modyfied;
#Basic
#Column(name = "first_name")
private String firstName;
#Basic
#Column(name = "last_name")
private String lastName;
#Basic
#Column(name = "passport")
private String passport;
#Basic
#Column(name = "pesel")
private String pesel;
#Basic
#Column(name = "phone_nr1")
private String phoneNr1;
#Basic
#Column(name = "phone_nr2")
private String phoneNr2;
#Column(name = "hash")
private String hash;
// uni-directional many-to-one association to DictUserType
#ManyToOne
#JoinColumn(name = "status")
private DictUserStatus status;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<Email> emails = new HashSet<Email>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<Address> address = new HashSet<Address>(0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<ArchivePasswords> archivePasswords = new HashSet<ArchivePasswords>(
0);
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
private Set<HostsWhitelist> hostsWhitelist = new HashSet<HostsWhitelist>(0);
....
I have a DAO layer, the method of search by user ID.
public User findUser(long userId) throws UserNotFoundException {
User user = userDao.findUser(userId);
if (user == null) {
throw new UserNotFoundException("Could not find user with id = "
+ userId);
}
return user;
}
Why lazy fetching does not work?
You should post the stack trace you are receiving. Where is the LazyLoadingException occurring? On the user Object? Are you trying to access it from another Object?
Is this the notorious LazyInitializationException? If so, then you need to either traverse the Object graph manually in the service (assuming you DAO code snippet is actually a Service method and not the DAO itself), or research the OpenSessionInViewFilter (assuming you are using Spring).
If you want to fetch the user with emails.
#Transactional
public List getUserWithEmails(long userId){
User user = userDao.findUser(userId);
if (user == null) {
throw new UserNotFoundException("Could not find user with id = "
+ userId);
}
for(Email email:user.getEmails()){
email.getId();
}
return user;
}
The same procedure apply to other one-to-many sets. Just like others have stated, you need to add OpenSessionInView (Hibernate) filter or OpenEntityManagerInView (JPA)filter in web.xml
If not specified, lazy fet hing will not take place defaults to EAGER.
public #interface Basic
The simplest type of mapping to a database column. The Basic annotation can be applied to a persistent property or instance variable of any of the following types: Java primitive types, wrappers of the primitive types, String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[], Byte[], char[], Character[], enums, and any other type that implements java.io.Serializable.
The use of the Basic annotation is optional for persistent fields and properties of these types. If the Basic annotation is not specified for such a field or property, the default values of the Basic annotation will apply.
Example 1:
#Basic
protected String name;
Example 2:
#Basic(fetch=LAZY)
protected String getName() { return name; }
fetch
public abstract FetchType fetch
(Optional) Defines whether the value of the field or property should be lazily loaded or must be eagerly fetched. The EAGER strategy is a requirement on the persistence provider runtime that the value must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime. If not specified, defaults to EAGER.
Default:
javax.persistence.FetchType.EAGER
optional
public abstract boolean optional
(Optional) Defines whether the value of the field or property may be null. This is a hint and is disregarded for primitive types; it may be used in schema generation. If not specified, defaults to true.
Default:
true