#124 - Polished Querydsl binding example for plain Spring MVC usage.
Collapsed domain model into a single class. Merged application configuration classes into a canonical Application class. Tweaked UserInitializer to expose dedicated methods to make sure the parameter is only exposed if remote users are used. Removed JavaScript based frontend as it was basically testing the Spring Data REST exposed API but not the data obtained through the Spring MVC controller. We're going to create a dedicated example for the Spring Data REST integration.
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.users;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Value
|
||||
public class Address {
|
||||
|
||||
private String city;
|
||||
private String street;
|
||||
private String zip;
|
||||
|
||||
}
|
||||
@@ -15,27 +15,44 @@
|
||||
*/
|
||||
package example.users;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig extends WebMvcConfigurerAdapter {
|
||||
@SpringBootApplication
|
||||
public class Application extends WebMvcConfigurerAdapter {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
|
||||
// Configure resource handler explicitly to enable non-versioned
|
||||
// Webjars in Thymeleaf templates
|
||||
registry.addResourceHandler("/webjars/**").//
|
||||
addResourceLocations("classpath:/META-INF/resources/webjars/").//
|
||||
resourceChain(true);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceUrlEncodingFilter resourceUrlEncodingFilter() {
|
||||
return new ResourceUrlEncodingFilter();
|
||||
@Autowired UserRepository repo;
|
||||
|
||||
@PostConstruct
|
||||
void initialize() throws Exception {
|
||||
|
||||
// Import demo users from local CSV
|
||||
new UserInitializer(repo).initLocally();
|
||||
|
||||
// Import demo users from remote service
|
||||
// new UserInitializer(repo).initRemote(100);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.users;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Value
|
||||
public class Picture {
|
||||
|
||||
private String large;
|
||||
private String medium;
|
||||
private String small;
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package example.users;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Value;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
@@ -25,19 +26,26 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Data
|
||||
@Document
|
||||
public class User {
|
||||
|
||||
@Id private String username;
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
private String email;
|
||||
private String nationality;
|
||||
@JsonIgnore private String password;
|
||||
private @Id String username;
|
||||
private String firstname, lastname, email, nationality;
|
||||
private @JsonIgnore String password;
|
||||
|
||||
@JsonUnwrapped private Address address;
|
||||
private @JsonUnwrapped Address address;
|
||||
private Picture picture;
|
||||
|
||||
@Value
|
||||
public static class Address {
|
||||
String city, street, zip;
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class Picture {
|
||||
String large, medium, small;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package example.users;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import example.users.UserInitializer.Datasource;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class UserApp {
|
||||
|
||||
@Autowired UserRepository repo;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserApp.class, args);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
void initialize() throws Exception {
|
||||
new UserInitializer(repo).init(100, Datasource.LOCAL);
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,19 @@
|
||||
*/
|
||||
package example.users;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import example.users.User.Address;
|
||||
import example.users.User.Picture;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.batch.item.file.FlatFileItemReader;
|
||||
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
|
||||
import org.springframework.batch.item.file.separator.DefaultRecordSeparatorPolicy;
|
||||
@@ -29,23 +35,40 @@ import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Initialize {@link UserRepository} with sample data.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class UserInitializer {
|
||||
|
||||
private static final UriTemplate REMOTE_TEMPLATE = new UriTemplate(
|
||||
"https://randomuser.me/api/?results={numberOfUsers}&format=csv&nat=US");
|
||||
|
||||
private final UserRepository repository;
|
||||
|
||||
public enum Datasource {
|
||||
LOCAL, REMOTE;
|
||||
public void initLocally() throws Exception {
|
||||
|
||||
List<User> users = readUsers(new ClassPathResource("randomuser.me.csv"));
|
||||
|
||||
repository.deleteAll();
|
||||
repository.save(users);
|
||||
}
|
||||
|
||||
public UserInitializer(UserRepository repository) throws UnexpectedInputException, ParseException, Exception {
|
||||
this.repository = repository;
|
||||
public void initRemote(int numberOfUsers) throws Exception {
|
||||
|
||||
List<User> users = readUsers(new UrlResource(REMOTE_TEMPLATE.expand(numberOfUsers)));
|
||||
|
||||
repository.deleteAll();
|
||||
repository.save(users);
|
||||
}
|
||||
|
||||
private List<User> readUsers(Resource resource, int nrUsers) throws Exception {
|
||||
private static List<User> readUsers(Resource resource) throws Exception {
|
||||
|
||||
Scanner scanner = new Scanner(resource.getInputStream());
|
||||
String line = scanner.nextLine();
|
||||
@@ -64,25 +87,33 @@ public class UserInitializer {
|
||||
User user = new User();
|
||||
|
||||
user.setEmail(fields.readString("email"));
|
||||
user.setFirstname(fields.readString("first"));
|
||||
user.setLastname(fields.readString("last"));
|
||||
user.setFirstname(capitalize(fields.readString("first")));
|
||||
user.setLastname(capitalize(fields.readString("last")));
|
||||
user.setNationality(fields.readString("nationality"));
|
||||
|
||||
String city = Arrays.stream(fields.readString("city").split(" "))//
|
||||
.map(StringUtils::capitalize)//
|
||||
.collect(Collectors.joining(" "));
|
||||
String street = Arrays.stream(fields.readString("street").split(" "))//
|
||||
.map(StringUtils::capitalize)//
|
||||
.collect(Collectors.joining(" "));
|
||||
|
||||
try {
|
||||
user.setAddress(new Address(fields.readString("city"), fields.readString("street"), fields.readString("zip")));
|
||||
user.setAddress(new Address(city, street, fields.readString("zip")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
user.setAddress(new Address(fields.readString("city"), fields.readString("street"), fields
|
||||
.readString("postcode")));
|
||||
user.setAddress(new Address(city, street, fields.readString("postcode")));
|
||||
}
|
||||
|
||||
user.setPicture(new Picture(fields.readString("large"), fields.readString("medium"), fields
|
||||
.readString("thumbnail")));
|
||||
user.setPicture(
|
||||
new Picture(fields.readString("large"), fields.readString("medium"), fields.readString("thumbnail")));
|
||||
user.setUsername(fields.readString("username"));
|
||||
user.setPassword(fields.readString("password"));
|
||||
|
||||
return user;
|
||||
});
|
||||
|
||||
lineMapper.setLineTokenizer(tokenizer);
|
||||
|
||||
reader.setLineMapper(lineMapper);
|
||||
reader.setRecordSeparatorPolicy(new DefaultRecordSeparatorPolicy());
|
||||
reader.setLinesToSkip(1);
|
||||
@@ -91,7 +122,6 @@ public class UserInitializer {
|
||||
List<User> users = new ArrayList<>();
|
||||
User user = null;
|
||||
|
||||
int count = 0;
|
||||
do {
|
||||
|
||||
user = reader.read();
|
||||
@@ -100,23 +130,8 @@ public class UserInitializer {
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
} while (user != null && ++count < nrUsers);
|
||||
} while (user != null);
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
public void init(int nrUsers, Datasource source) throws Exception {
|
||||
|
||||
if (repository.count() != nrUsers) {
|
||||
|
||||
Resource resource = Datasource.LOCAL.equals(source) ? new ClassPathResource("randomuser.me.csv")
|
||||
: new UrlResource("https://randomuser.me/api/?results=" + nrUsers + "&format=csv");
|
||||
|
||||
List<User> users = readUsers(resource, nrUsers);
|
||||
if (!users.isEmpty()) {
|
||||
repository.deleteAll();
|
||||
repository.save(users);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,11 +23,20 @@ import org.springframework.data.repository.CrudRepository;
|
||||
import com.mysema.query.types.path.StringPath;
|
||||
|
||||
/**
|
||||
* Repository to manage {@link User}s. Also implements {@link QueryDslPredicateExecutor} to enable predicate filtering
|
||||
* on Spring MVC controllers as well as {@link QuerydslBinderCustomizer} to tweak the way predicates are created for
|
||||
* properties.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface UserRepository extends CrudRepository<User, String>, QueryDslPredicateExecutor<User>,
|
||||
QuerydslBinderCustomizer<QUser> {
|
||||
public interface UserRepository
|
||||
extends CrudRepository<User, String>, QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.binding.QuerydslBinderCustomizer#customize(org.springframework.data.querydsl.binding.QuerydslBindings, com.mysema.query.types.EntityPath)
|
||||
*/
|
||||
@Override
|
||||
default public void customize(QuerydslBindings bindings, QUser root) {
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package example.users.web;
|
||||
|
||||
import example.users.User;
|
||||
import example.users.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -22,28 +24,37 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.querydsl.QuerydslPredicate;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
|
||||
import com.mysema.query.types.Predicate;
|
||||
|
||||
import example.users.User;
|
||||
import example.users.UserRepository;
|
||||
|
||||
/**
|
||||
* Controller to handle web requests for {@link User}s.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class UserController {
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired) )
|
||||
class UserController {
|
||||
|
||||
private final UserRepository repository;
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
|
||||
String index(Model model, //
|
||||
@QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable, //
|
||||
@RequestParam MultiValueMap<String, String> parameters) {
|
||||
|
||||
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentRequest();
|
||||
builder.replaceQueryParam("page", new Object[0]);
|
||||
|
||||
model.addAttribute("baseUri", builder.build().toUri());
|
||||
model.addAttribute("users", repository.findAll(predicate, pageable));
|
||||
|
||||
return "index";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user