I am designing an API using Spring boot for some social network backend. My current model looks like this:
public class User {
private long id;
private String handle;
private String name;
private List<User> followers;
private List<User> following;
// Getters setters etc
Now, I have created DTO which closely resembles above structure. My problem is that sometimes I want to return exactly what's above (which is fine) but sometimes, I don't want that.
For example when someone is only interested in finding followers of user, I don't want to include followers and following (I am simply interested in id, handle and name so computing followers and following for all of those users would be an incredible waste of resources).
In my current implementation those fields are returned with null values, which I don't think is a great idea. Should I create a separate DTO without those lists with just id, handle and name? Or is there more elegant way to do it?
It is a controversial issue. If you don't want to create separate dto there are several ways to do it. It depends on what data access approach you are going to use:
Using Spring Data JPA it is possible to return an entity in projection. You just need to add an additional constructor to your entity:
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select new User(u.id,u.name) from User u")
List<User> findAllUserItems();
}
Or same using JPA EntityManger:
public List<User> findAllUserItems() {
return entityManager.createQuery("select new User(u.id,u.name) from User u", User.class)
.getResultList();
}
If you wonder about unnecessary null fields and you are using Jackson, it is possible to configure to ignore null fields. For Spring Boot:
spring.jackson.default-property-inclusion=non_null
Or with Java config:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
Or for not Spring Boot project:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
}
#Bean
public HttpMessageConverter converter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
Also, if you going to use Hibernate Session. To map directly to dto you may use AliasToBeanResultTransformer:
public List<UserDto> findAllUserItems() {
return session.createQuery("select u.id as id,u.name as name from User u")
.setResultTransformer(Transformers.aliasToBean(UserDto.class))
.list();
}
Suppose you can try like this.
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne
private User parent;
#OneToMany(mappedBy="parent")
private List<User> followers;
#OneToMany(mappedBy="parent")
private List<User> following;
// Getters, Setters etc....
}
Also this post may be helpful.
Related
I am trying to retrieve a user's particular order.
This is how I retrieve the user's orders in my OrderController
#GetMapping("/{id}/orders")
public List<Order> findAll(#PathVariable Long id) throws UserNotFoundException {
Optional<User> existingUser = this.userRepository.findById(id);
if (existingUser.isEmpty()) {
throw new UserNotFoundException("User not found");
}
return existingUser.get().getOrders();
}
With the RequestMapping
#RestController
#RequestMapping("/api/users")
public class OrderController {(...)}
This is the OneToMany relationship
User Entity
// ONE TO MANY
#OneToMany(mappedBy = "user")
private List<Order> orders;
Order Entity
// MANY TO ONE
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private User user;
The UserRepository and OrderRepository interface, both extend JpaRepository
I do manage to retrieve all the user's orders through Postman
I am now wondering, how can I retrieve a user's particular order?
So for instance,
as shown in the image let's say I would only like to retrieve the order with the id of 2, in this particular address :
http://localhost:8070/api/users/1/orders/2
How can I make it please ?
Create an endpoint with
#GetMapping("/{id}/orders/{orderId}")
and return the particular order.
Create an OrderRepository and simply create the
public Order findByIdAndUserId(long orderId,long userId);
interface method for retrieving the given one.
Just a remark: you should validate that the given user is the same as the logged in one. What happen if I send a request to the backend, where I rewrite the user id to someone else's id?
try this,
#GetMapping("/{userId}/orders/{orderId}")
public Order findAll(#PathVariable Long userId, #PathVariable Long orderId) throws Exception {
//your code
}
Obviously you should had a Get mapping:
#GetMapping("/{userId}/orders/{orderId}")
public List<Order> findAll(#PathVariable Long userId, #PathVariable Long orderId) throws UserNotFoundException {
...
}
And for the requesting, you have three options:
Call you this.userRepository.findById(id) and filter after your order.
Create an OrderRepository to limit the request to the order table. But you need to have a reference to the user and you will probably not improve any performance. I would not advise that.
Add a query in your order repository to query the orderId for the userId:
#Repository
public interface UserRepository extends JpaRepository<.., ..> {
#Query("select ... from User user join user.orders ... where ...user = :userId and order = :orderId")
Order getUserOrder(#Param("userId") String userId, #Param("orderId") String orderId);
}
Anyway, You should create a service and inject it in the controller to encapsulate the search/filtering complexity and let you endpoints code clean (#Autowired private OrderService orderService):
#RestController
#RequestMapping("/api/users")
public class OrderController {
#Autowired
private OrderService orderService;
}
and:
#Service
public class OrderService {
}
I would like to have the roles set fetch lazily. Hibernate fetchType.Lazy doesn't work for this cases, where spring data is used. I 've been trying lots of possibilities like Entitygraph but none of them works on this, or I'm using them wrong.
I have the next classes:
A class User:
#Entity
#JsonRootName(value = "user")
#Table(name = "web_users", schema = "t_dw_comercial")
public class User {
#Id
private int userId;
private String fullName;
#OneToMany(fetch=FetchType.LAZY)
#JoinTable(name="web_users_roles",
joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")}
)
private List<Role> roles;
}
A class Role:
#Entity
#JsonRootName(value = "roles")
#Table(name = "web_roles", schema = "t_dw_comercial")
public class Role {
#Id
private int roleId;
private String roleName;
}
Service:
#Service
public class UserService implements IUserService{
#Autowired
UserRepository repository;
public User findUserByLdapId(String loginName) {
return repository.findUserByLdapId(loginName);
}
}
Repository:
#Repository
public interface UserRepository extends CrudRepository<User, Long>{
#Query("SELECT u FROM User u where u.ldapId= ?1")
public User findUserByLdapId(String loginName);
}
Controller:
#Controller
#RestController
public class UserController {
#Autowired
private IUserService userService;
#CrossOrigin
#RequestMapping(value = "/dashboard", params = {"user"}, method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<User> getUser(#RequestParam(value = "user") String ldapId) {
User user = userService.findUserByLdapId(ldapId);
if(user == null)
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
return new ResponseEntity<>(user, HttpStatus.OK);
};
}
So a json would looks like:
{
"user": {
"userId": 1,
"fullName": "Carolina Ponce",
"roles":[]
}
}
Thanks in advance!
It seems you're asking for two different things: how to fetch #OneToMany association lazily (which should work out of the box) and how to make your JSON look like the above (which has nothing to do with how your JPA entity fetching is configured).
If you serialize your entity using Jackson, by default all the fields will get serialized, including those that are fetched lazily. If the persistence context is still open when you begin to serialize the entities, Jackson will simply trigger lazy loading by accessing the property. As a result, regardless of whether you use FetchType.EAGER or FetchType.LAZY, roles will be included in the result (I assume it is the case, since you'd be getting a LazyInitializationException if the context was closed).
The solution is, simply, to tell Jackson to refrain from serializing roles using #JsonIgnore if you don't want the property in the result.
First of all Hibernate and Spring Data are totally different tools designed for different purposes and they work with each other just fine. You can read more about the differences between both here: What Is the Difference Between Hibernate and Spring Data JPA?
Spring Data has nothing to do with how you fetch entity's related collections, Hibernate, as an implementation of JPA, does.
I assume you indeed fetch your collection lazily and that happens during serialization of your entity so the mechanism works as it should.
I suggest you to create some sort of DTO object that you could return from your controller rather than returning an entity. It seems like you don't want to expose user's roles so creating a DTO without roles may be a good idea. Other solution is to instruct Jackson to ommit roles property during serialization using #JsonIgnore annotation.
Mongodb is a no-schema document database, but in spring data, it's necessary to define entity class and repository class, like following:
Entity class:
#Document(collection = "users")
public class User implements UserDetails {
#Id private String userId;
#NotNull #Indexed(unique = true) private String username;
#NotNull private String password;
#NotNull private String name;
#NotNull private String email;
}
Repository class:
public interface UserRepository extends MongoRepository<User, String> {
User findByUsername(String username);
}
Is there anyway to use map not class in spring data mongodb so that the server can accept any dynamic JSON data then store it in BSON without any pre-class define?
First, a few insightful links about schemaless data:
what does “schemaless” even mean anyway?
“schemaless” doesn't mean “schemafree”
Second... one may wonder if Spring, or Java, is the right solution for your problem - why not a more dynamic tool, such a Ruby, Python or the Mongoshell?
That being said, let's focus on the technical issue.
If your goal is only to store random data, you could basically just define your own controller and use the MongoDB Java Driver directly.
If you really insist on having no predefined schema for your domain object class, use this:
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
private Map<String, Object> schemalessData;
// getters/setters omitted
}
Basically it gives you a container in which you can put whatever you want, but watch out for serialization/deserialization issues (this may become tricky if you had ObjectIds and DBRefs in your nested document). Also, updating data may become nasty if your data hierarchy becomes too complex.
Still, at some point, you'll realize your data indeed has a schema that can be pinpointed and put into well-defined POJOs.
Update
A late update since people still happen to read this post in 2020: the Jackson annotations JsonAnyGetter and JsonAnySetter let you hide the root of the schemaless-data container so your unknown fields can be sent as top-level fields in your payload. They will still be stored nested in your MongoDB document, but will appear as top-level fields when the ressource is requested through Spring.
#Document(collection = "users")
public class User implements UserDetails {
#Id
private String id;
// add all other expected fields (getters/setters omitted)
private String foo;
private String bar;
// a container for all unexpected fields
private Map<String, Object> schemalessData;
#JsonAnySetter
public void add(String key, Object value) {
if (null == schemalessData) {
schemalessData = new HashMap<>();
}
schemalessData.put(key, value);
}
#JsonAnyGetter
public Map<String, Object> get() {
return schemalessData;
}
// getters/setters omitted
}
I have one simple class
#Entity
#Table(name="user")
public class User implements Serializable {
#Id
#GeneratedValue
private Integer Id;
#Length(min = 5, message = "Username must be at least 5 characters long.")
#Column(name="username",nullable=false,unique=true)
private String userName;
#ManyToMany(cascade= {CascadeType.PERSIST},fetch=FetchType.EAGER)
#JoinTable(name="user_user_profile")
private Set<UserProfile> userProfile = new HashSet<>();
}
And second class:
#Entity
#Table(name = "user_profile")
public class UserProfile {
#javax.persistence.Id
#GeneratedValue
private int Id;
#Column(name = "type", nullable = false, unique = true)
#Enumerated(EnumType.STRING)
private UserProfileType type = UserProfileType.USER;
}
public enum UserProfileType {
USER("USER"),
ADMIN("ADMIN");
}
I'm using Spring MVC and Spring Secuirty with Hibernate. Is there any way to on start of the app make every possible entry in UserProfile Entity (there is only two)? Do I have to get UserProfile from database (via TypedQuery or EntityManager.find() ) and then add it to the User to not make any exceptions?
The enum items are static in your application, so I wouldn't try to make automatic changes in the database. Adding a new record is trivial, but removing an item that is already referenced may need individual care. These values are essential for your application, so I think they should be included in your SQL scripts.
If you are using DB versioning tools such as Flyway or Liquibase, add/remove records of the user_profile table in the migration scripts. They can be configured to run the migrations before your application (and Hibernate) starts, so the application will always see the correct data.
You can add a application start up event and persist the user profiles. You can delete all the user profiles before the application shut down as well. But I wouldn't recommend this as I assume the UserProfiles wouldn't change frequently. If that is the case, you are better off preloading the user profiles via some sql script as suggested in the other answer. If you really want to do it via app, the safest way would be to delete before the app gets shut down. Following is the sample snippet. I assume you are using spring-data-jpa and provided the snippet.
#Component
public class AppStartedListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private UserProfileRepository repository;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
for(UserProfileType userProfileType: UserProfileType.values()) {
UserProfile up = new UserProfile(userProfileType);
repository.save(up);
}
}
}
#Component
public class AppStoppedListener implements ApplicationListener<ContextClosedEvent> {
#Autowired
private UserProfileRepository repository;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
repository.deleteAll();
}
}
public interface UserProfileRepository extends CrudRepository<UserProfile, Integer> {
}
So I added method to dao layer:
#Transactional
#EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
UserProfile user=new UserProfile();
em.persist(user);
UserProfile admin=new UserProfile();
admin.setType(UserProfileType.ADMIN);
em.persist(admin);
}
And now, before adding new User i just use HQL to get persistent UserProfile object that I can add to my User. Altough it works I will probably try to load it from some sort of *.sql file since I had to add method metioned above to the Dao layer interface (because of interface type proxy) and I don't like it to be honest.
Premise:
I chose to do this because I might end up having a few thousand schemas, each having (amongst others) 1 table with a few million entries. The alternative was having (amongst others) one table with a few billion entries in one schema.
The best way to elaborate on this question is providing a simple example. Consider the following:
User.java
#Entity(name = "user")
public class User {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "username")
private String username;
// getters and setters...
}
UserDao.java
#Repository
public interface UserDao extends CrudRepository<User, Long> {}
UserService.java
public interface UserService {
User getUser(Long id);
}
UserServiceBean.java
#Transactional
#Service
public class UserServiceBean implements UserService {
#Autowired
private UserDao dao;
#Override
public User getUser(Long id) {
return dao.findOne(id);
}
}
UserController.java
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(
#PathVariable("id") Long id) {
User user = userService.getUser(id);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
}
I would like to extend to the following functionality: supplying another ID in the URL in order to return user data from a different table.
UserController.java
...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(
#PathVariable("id") Long id,
#RequestParam(value = "tlbid") Long tblId) {
User user = userService.getUser(id, tblId);
return new ResponseEntity<User>(user, HttpStatus.OK);
}
Now the UserService will decode that ID into something that could be useful for spring in order to get the data from a different table.
UserServiceBean.java
...
public User getUser(Long id, Long tblId) {
Object o = doMagic(tblId);
// What should 'o' be and how could I use this?
}
All the tables have the same structure and names but different entries. The tables have to be on a different database, or in the same database but on a different schema.
I would like to know either:
a) How can I have one database connection and specify a different schema for every request.
b) How to create new database connections when necessary (I would maintain them for further requests), and specify on which connection should the request be made each time.
c) My premises are wrong and having billions of entries in a table and high concurrency does not significantly slow down query speeds.
It sounds like you're describing a multi-tenant solution. See the Hibernate documentation for a longer description and a few options for how you could partition your data.
Note: we are trying to implement the schema-based multi-tenant approach at the moment :)
In case if you are using hibernate entity class then you can always use different schemas for same datasource provided that the other schemas are accessible for the particular user mapped in Datasource.
you can use schema attribute of Table annotation in your entity class. Use the below syntax for using different schema
#Table(name="TABLE_NAME",schema="SCHEMA2")