1.rest风格增删改查及druid数据源的配置及mybatis的配置
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)//lombok
public class Book implements Serializable {
private Integer id;
private String name;
private Integer price;
}
public interface BookMapper {
@Select("select isbn id,book_name name,price from books where isbn=#{id}")
public Book getBookById(Integer id);
@Update("<script>" +
"update books " +
"<set>" +
"<if test= \"name!=null\">" +
"book_name=trim(#{name})," +
"</if>" +
"<if test= \"price!=null\">" +
"price=trim(#{price})," +
"</if>" +
"</set>"+
"where isbn=#{id} </script>")
public void updateBook(Book book);
@Delete("delete from books where isbn=#{id}")
public void deleteBookById(Integer id);
@Insert("insert into books(isbn,book_name,price) values(trim(#{id}),trim(#{name}),trim(#{price}))")
@Options(useGeneratedKeys = true)
public void insertBook(Book book);
}
@org.springframework.web.bind.annotation.RestController
public class RestController {
@Autowired
private BookMapper bookMapper;
@GetMapping("/book/{id:\\d+}")
public Book get(@PathVariable Integer id){
return bookMapper.getBookById(id);
}
@PutMapping("/book")
public Book put(Book book){
bookMapper.updateBook(book);
Book bookNew = bookMapper.getBookById(book.getId());
return bookNew;
}
@DeleteMapping("/book/{id:\\d+}")
public String post(@PathVariable Integer id){
bookMapper.deleteBookById(id);
return id+"删除成功";
}
@PostMapping("/book")
public Book post(Book book){
bookMapper.insertBook(book);
Book bookNew = bookMapper.getBookById(book.getId());
return bookNew;
}
}
@Configuration
@ComponentScan(value = "springmvc.demo")
@MapperScan("springmvc.demo.dao")
public class MVCConfig {
}
@Configuration
public class MVCConfigBean {
@ConfigurationProperties(prefix ="spring.datasource")
@Bean
public DataSource getDataSource(){
return new DruidDataSource();
}
//配置druid监控页面
@Bean
//添加servlet
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean<StatViewServlet> servlet = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
HashMap<String, String> map = new HashMap<>();
map.put("loginUsername", "admin");//账户
map.put("loginPassword", "123456");//密码
map.put("allow", "");//允许所以用户登录,默认允许所有用户登录
map.put("deny", "");//拒绝用户登录,可以是ip地址等
servlet.setInitParameters(map);
return servlet;
}
@Bean
//添加过滤器
public FilterRegistrationBean webStatFilter(){//假如拦截所有那么与标注@component注解作用一样,其他的也是
FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
filter.setFilter(new WebStatFilter());
HashMap<String, String> map = new HashMap<>();
map.put("exclusions", "*.js,*.css,*/druid/*,*.gif,*.jpg,*.png,*.ico");
filter.setInitParameters(map);
filter.setUrlPatterns(Arrays.asList("/*"));
return filter;
}
//添加配置mybatis的配置
@Bean
public ConfigurationCustomizer configurationCustomizer(){
ConfigurationCustomizer configurationCustomizer = x->{
//设置驼峰命名法
x.setMapUnderscoreToCamelCase(true);
};
return configurationCustomizer;
}
}
#编码处理
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
#日志
logging.level.springmvc.demo.mvcdemo=debug
#与mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false&verifyServerCertificate=false
spring.datasource.username=root
spring.datasource.password=cgz12345678
#使用druid连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#druid连接池连接池参数配置
#初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
spring.datasource.initialSize=5
#最大连接池数量
spring.datasource.maxActive=10
#获取连接时最大等待时间
spring.datasource.maxWait=3000
#最小连接池数量
spring.datasource.minIdle=3
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
#验证数据库连接的有效性
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
#stat功能(监控)、wall功能(sql防火墙)、logback功能(监控日志输出),需要配置添加相应的配置文件否则会报错
spring.datasource.filters=stat,wall,logback
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
@RunWith(SpringRunner.class)
@SpringBootTest
public class MvcDemoApplicationTests {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
Logger logger;
@Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
logger=LoggerFactory.getLogger(MvcDemoApplicationTests.class);
}
@Test
public void testGet() throws Exception {
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.get("/book/1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)).andReturn()
.getResponse().getContentAsString();
logger.debug(contentAsString);
System.out.println(contentAsString);
}
@Test
public void testInsert() throws Exception {
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.post("/book")
.param("name", " dokcer ")
.param("price", "150")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn().getResponse().getContentAsString();
System.out.println(contentAsString);
}
@Test
public void testDelete() throws Exception {
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.delete("/book/1003")
.contentType(MediaType.APPLICATION_JSON_UTF8)).andReturn()
.getResponse().getContentAsString();
System.out.println(contentAsString);
}
@Test
public void testUpdate() throws Exception {
String contentAsString = mockMvc.perform(MockMvcRequestBuilders.put("/book")
.param("id","1009")
.param("price", "1500")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn().getResponse().getContentAsString();
System.out.println(contentAsString);
}
}
检查数据源是否配置成功
druid监控页面
2.@Pathvariable注解
2.1配置正则实例
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true) //lombok
public class User implements Serializable {
private String name;
}
@RestController //responsebody +controller
public class UserController {
@GetMapping("/user/{name:.*}")
public User testUser(@PathVariable(required = false,value = "name") User user){
System.out.println(user.getClass());
return user;
}
}
@SpringBootApplication
public class MvcDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MvcDemoApplication.class, args);
}
}
@Configuration
@ComponentScan(value = "springmvc.demo")//扫描包
public class MVCConfig {
}
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
3.@valid与@Validated
@valid与@Validated的区别,@Validated是spring中的,是在@valid基础上而来的,在@valid的基础上增加分组功能,这里就直接说@Validated,没有加分组都需要认证,加了分组只有符合分组的才需要认证,一般不使用分组
3.1基本使用
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
public class User implements Serializable {
@NotNull(message="不能为空")//含有属性groups
private String name;
}
@RestController
public class UserController {
@GetMapping("/user")
public User testUser(@Validated User user, BindingResult result){
result.getAllErrors().stream().forEach((x)->{
System.out.println(x.getObjectName()+":"+x.getDefaultMessage());
});
return user;
}
}
输出结果:user:不能为空
备注:假如没有BindingResult result,那么在进入testUser之前就会被拦截,message="不能为空"并没有什么作用,加入BindingResult result之后,才会进入testUser方法,在没有进入方法时的异常页面见后该篇4
3.2自定义符合valid的注解
这里定义的是@NoNull注解
//会自动加载,不需要其他的配置
public class MyValidAnnotation implements ConstraintValidator<MyNotNull,Object> {
/*
obj:需要验证的参数
*/
@Override
public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) {
System.out.println(obj);
return false;
}
@Override
public void initialize(MyNotNull constraintAnnotation) {
}
}
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy =MyValidAnnotation.class )//添加在@validated的必要注解
public @interface MyNotNull {
String message() default "{javax.validation.constraints.NotNull.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
public class User implements Serializable {
@MyNotNull(message="不能为空---MyNotNull")
private String name;
}
@RestController
public class UserController {
@GetMapping("/user")
public User testUser(@Validated User user, BindingResult result){
result.getAllErrors().stream().forEach((x)->{
System.out.println(x.getObjectName()+":"+x.getDefaultMessage());
});
return user;
}
}
4.异常消息的处理
4.1验证异常消息的处理上述已经说明
4.2直接被拦截的异常处理
springboot会相应的转向我们在resources下新建的resources的error的相应的错误代码的页面
还可以是如下这样,表示以4开头的错误页面,例如
4.3运行过程中出现的异常
4.3.1直接抛出异常
@GetMapping("/user/error")
public User testUser1(@Validated User user, BindingResult result) throws Exception {
throw new Exception("出错了");
}
4.3.2 throw自定义异常类
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR,reason = "内部出错了!")
public class MyException extends RuntimeException {
}
@GetMapping("/user/error")
public User testUser1(@Validated User user, BindingResult result) throws Exception {
throw new MyException();
}
4.3.3 使用@ExceptionHandler
注解
@ResponseBody
@GetMapping("/testExceptionHandler")
public User testUser1(@Validated User user, BindingResult result) throws Exception {
int i=10/0;
return user;
}
@ExceptionHandler({Exception.class})
public String testExceptionHandler(){
return "redirect:/error.html"; //重定向,/站点目录
}
4.3.4 修改状态码及原因,@ResponseStatus也能在
@ExceptionHandler({Exception.class})
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR,reason = "内部错误")
public void testExceptionHandler(){
}
备注:这样之后不会去相应的页面,也就是加入有返回值例如return "redirect:/error.html";,会失效
4.3.5@ControllerAdvice定制全局的异常,类中的@ExceptionHandler优先级高于@ControllerAdvice中的@ExceptionHandler优先级
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler({Exception.class})
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR,reason = "内部错误")
public void testExceptionHandler(){
}
}
5.jsonview
@NoArgsConstructor
@AllArgsConstructor
@Data
@Accessors(chain = true)
public class User implements Serializable {
public interface UserSimpleView{} ;
public interface UserDetailView extends UserSimpleView{} ;
@JsonView(UserSimpleView.class)
private String username;
@JsonView(UserDetailView.class)
private Integer password;
}
@RestController
public class UserController {
@Autowired
UserMapper userMapper;
@GetMapping("/user/{username}")
@JsonView(User.UserSimpleView.class)
public User testGet(@PathVariable String username){
User user = userMapper.getUserById(username);
return user;
}
}
6.异步
6.1直接使用callable
@RestController
public class AsyncController {
@Autowired
UserMapper userMapper;
@GetMapping("/async/{username}")
@JsonView(User.UserSimpleView.class)
public Callable<User> get(@PathVariable String username){
long id = Thread.currentThread().getId();
long start= Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli();
Callable<User> callable = ()->{
long id1 = Thread.currentThread().getId();
long start1= Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli();
User user = userMapper.getUserById(username);
long end1= Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli();
System.out.println("副线程"+id1+":"+(end1-start1));
return user;
};
long end= Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toInstant().toEpochMilli();
System.out.println("主线程"+id+":"+(end-start));
return callable;
}
}
6.2 使用DeferredResult实现异步
@Bean("queue")
public ConcurrentLinkedQueue<String> concurrentLinkedQueue() throws IOException {
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
return queue;
}
@Bean("map")
public Map<String, DeferredResult<String>> map(){
return new HashMap();
}
@Autowired
ConcurrentLinkedQueue<String> queue;
@Autowired
Map map;
@GetMapping("/async/deferredResult")
public Object message(@RequestParam(required = false) String message){
if(StringUtils.isNotBlank(message)){
queue.add(message);
DeferredResult<String> result = new DeferredResult<>();
map.put(message, result);
return result;
}
return "输入格式不正确";
}
@Component
public class QueueListener implements ApplicationListener {//启动监听器
@Autowired
ConcurrentLinkedQueue<String> queue;
@Autowired
Map<String, DeferredResult<String>> map;
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
new Thread(()->{
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(queue.size()>0){
String msg = queue.poll();
System.out.println(msg);
map.get(msg).setResult(msg);
map.remove(msg);
}else {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
7.swagger
依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
添加注解
@EnableSwagger2
public class ControllerTest {
访问页面
http://127.0.0.1:8080/swagger-ui.html
增加描述信息
public class Book implements Serializable {
private Integer id;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("价格")
private Integer price;
}
@GetMapping("/book/{id:\\d+}")
public Book get(@PathVariable @ApiParam("书本id") Integer id){
return bookMapper.getBookById(id);
}
8.拦截器
8.1 HandlerInterceptor
@Component
public class MyHandlerIntercepter implements HandlerInterceptor {
//在调用目标方法之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;//false表示后续拦截器不在执行
}
//在调用目标方法之后, 但渲染视图之前,
// 可以对请求域中的属性或视图做出修改.,出现异常不会执行该方法
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//能获取类名,方法名,但是不能获取方法的参数
System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
System.out.println(((HandlerMethod)handler).getMethod().getName());
}
//之后执行,出现异常也会执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
@Configuration
@ComponentScan(value = "springmvc.demo")
@MapperScan("springmvc.demo.dao")
public class MVCConfig implements WebMvcConfigurer {
@Autowired
MyHandlerIntercepter myHandlerIntercepter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myHandlerIntercepter);
}
}
8.2 AOP(拦截的增强版)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Aspect
@Component
public class MyAOP {
@Pointcut("execution(* springmvc.demo.controller..*(*))")
public void pointcut(){}
@Around("pointcut()")
public Object before(ProceedingJoinPoint joinPoint) {
try {
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(System.out::println);
Object proceed = joinPoint.proceed();//相当于拦截器的dochain方法,否则不会执行下去
return proceed;//返回结果后才会有值
} catch (Throwable throwable) {
return throwable.getMessage();
}
}
}