【Spring】SpringBoot学习手册

目录

一、注解

1.1 @RequestMapping: 配置URL映射

1.2 @ResponseBody 和 @RequestBody

1.3 @RestController:Api获取的 JSON 数据自动转换

1.4 @Controller 

1.5 @Autowired

二、http请求相关

2.1 Get方式请求

2.2 POST方式请求

三、Jackson 处理

四、同个文件加载顺序

4.1 加载顺序

4.2 自定义加载文件夹和加载顺序

4.3 直接url访问html访问不了

五、使用MultipartFile实现文件上传

5.1 实现上传代码

5.2 实现上传大小限制

5.3 部署成jar包,并指定上传目录

5.4 性能优化

六、打包后,使用分离的配置文件

七、注解自动映射到属性和实体类

7.1 使用 @PropertySource 和 @Value 映射属性

7.2 通过实体类配置

八、全局异常

九、将项目打包成 war 包,并在 tomcat 运行

9.1 打包

9.2 tomcat 下运行

十、过滤器 Filter、自定义Servlet、监听器Listener

10.1 Filter

10.2 自定义 Servlet

10.3 监听器 Listener

10.4 拦截器

10.4 调用顺序

10.5 过滤器和拦截器的区别和使用

十一、MyBatis 使用

11.1 什么是 Mybatis

11.2 Mybatis 的使用 -- 使用 java 方式

11.3 Mybatis 的使用 -- 使用 xml 方式

11.4 Mybatis 的使用 -- 事务

十二、Redis 使用

12.1 安装和配置

12.2 使用

12.3 可视化工具 Redis Desktop Manager

12.4. 封装


一、注解


1.1 @RequestMapping: 配置URL映射

@RequestMapping 用于定义Api接口和逻辑方法的映射关系

@RestController
@RequestMapping("/home ") // 配置在类上
public class IndexController {

    @RequestMapping(value = {
        " ",
        "/page ",
        "page* ",
        "view/*,**/msg "
    })
    String indexMultipleMapping() {
        return "Hello from index multiple mapping. ";
    }

    @RequestMapping(value="/hello",method= RequestMethod.GET) // 配置在处理器上
    public String sayHello(){
        return "hello";
    }
}

 

1.2 @ResponseBody 和 @RequestBody

@ResponseBody 的作用是将 controller 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到 response 对象的body区,通常用来返回JSON数据或者是 XML。需要配合 @Controller 使用

@RequestBody 用于将 Controller 的方法参数,根据 HTTP Request Header 的 content-Type 的内容,将其转换成 Bean 对象。

通常用来处理数据类型为 application/json 或者是 application/xml 的内容

@RequestMapping(value = "/testRequestBody", method= RequestMethod.POST)
@ResponseBody
public Person testRequestBody(@RequestBody Person p) {
    System.out.println("creating a employee:" + p);
    return p;
}

 

1.3 @RestController:Api获取的 JSON 数据自动转换

@RestController是 springboot4.0 新加入的注解,原来返回 JSON 需要 @ResponseBody 和 @Controller 配合,但现在只需要使用 @RestController 就可以。即 @RestController = @ResponseBody + @Controller。同时不受 @Controller 需要模板配合的限制

@SpringBootApplication
@RestController
public class HelloSpringApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloSpringApplication.class, args);
	}

	/*
    *  使用bean对象传参
    *  使用body传输数据
    */
    @RequestMapping("/v2/save_user")
    public Object save_user(@RequestBody User user) {
        params.clear();
        params.put("user", user);
        return params;
    }
}

 

1.4 @Controller 

@Controller 用于标记在一个类上,将它标记的类成为一个 SpringMVC Controller 对象。分发处理器会扫描使用了该注解的类的方法,并在符合条件时执行相应的动作。

@Controller
public class SampleController {

    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World";
    }

    @RequestMapping("/test")
    public Map<String,String> testMap() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "xiaozhang");
        map.put("age", "18");
        return map;
    }
}

 

1.5 @Autowired

@Autowired 是用于自动装配参数,避免使用 setter 参数来赋值。通常用户赋予配置文件的变量到配置实体类中。

 

二、http请求相关


2.1 Get方式请求

@RestController
public class GetController {
    private Map<String, Object> params = new HashMap<>();

    @GetMapping(path = "/{city_id}/{user_id}")
    public Object findUser(@PathVariable("city_id") String cityId, @PathVariable("user_id") String userId) {
        params.clear();
        params.put("cityId", cityId);
        params.put("userId", userId);
        return params;
    }

    @GetMapping(path = "/v1/page_user")
    public Object findUser(int from, int to) {
        params.clear();
        params.put("from", from);
        params.put("to", to);
        return params;
    }

    @GetMapping(path = "/v2/page_user")
    public Object page_from(@RequestParam(defaultValue = "0", name="key") int from, int to) {
        params.clear();
        params.put("from", from);
        params.put("to", to);
        return params;
    }

    @GetMapping("/v2/get_header")
    public Object save_user(@RequestHeader("access_token") String accessToken, String id) {
        params.clear();
        params.put("accessToken", accessToken);
        params.put("id", id);
        return params;
    }

    // 使用servlet方式获取参数
    @GetMapping("/v2/test_header")
    public Object test_user(HttpServletRequest request) {
        params.clear();
        String id = request.getParameter("id");
        params.put("id", id);
        return params;
    }
}

 

2.2 POST方式请求

主要使用有 POST(提交),PUT(更新), DELETE(删除)。

@RestController
public class OtherHttpController {
    private Map<String,Object> params = new HashMap<>();

    // 提交
    @PostMapping("/post/testUser")
    public Object test_user_post(@RequestBody User user) {
        params.clear();
        params.put("user", user);
        return params;
    }

    // 更新
    @PutMapping("/put/testUser")
    public Object test_user_put(@RequestBody User user) {
        params.clear();
        params.put("user", user);
        return params;
    }

    // 删除
    @DeleteMapping("/del/testUser")
    public Object test_user_del(@RequestBody User user) {
        params.clear();
        params.put("user", user);
        return params;
    }
}

 

三、Jackson 处理


指定字段不返回:  @JsonIgnore

指定日期格式: @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",local="zh",timezone="GMT+8")

空字段不返回:@JsonInclude(Include.NON_null)

指定别名:@JsonProperty

 

四、同个文件加载顺序


4.1 加载顺序

对同个文件的加载顺序,springboot 会挨个查找:

META/resources > resouces > static > public,查找是否存在相应的资源,如果有则直接返回

 

4.2 自定义加载文件夹和加载顺序

application.properties 文件中配置:

spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ 

 

4.3 直接url访问html访问不了

明明有这个html,直接尝试访问html会报错

这是因为没有存放在默认加载的文件夹下(resources、public、static),需要使用模板引擎:

1)在pom.xml文件中,引入 thymeleaf

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2)使用接口方式返回页面

@Controller
public class fileController {

    @RequestMapping("/api/v1/goindex")
    public Object index() {
        return "index";
    }
}

 

五、使用MultipartFile实现文件上传


5.1 实现上传代码


@RestController
public class fileController {
    private static final String filePath = "E:/study/Java/hello-spring/src/main/resources/static/images/";

    @RequestMapping("/upload")
    public ResponseData upload(@RequestParam("head_img")MultipartFile file, HttpServletRequest request) {
        String name = request.getParameter("name");
        System.out.println("用户名: " + name);

        // 获取文件名
        String fileName = file.getOriginalFilename();
        System.out.println("上传的文件名为: " + fileName);

        // 获取文件名的后缀名
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        System.out.println("上传的后缀名为: " + suffixName);

        // 获取文件上传的路径
        fileName = UUID.randomUUID() + suffixName;
        System.out.println("转换后的名称为: " + fileName);
        File dest = new File(filePath + fileName);
        try {
            file.transferTo(dest);
            return new ResponseData(0, fileName);
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponseData(-1, null, "上传失败");
    }
}
package tom.spring.hellospring.domain;

import java.io.Serializable;

public class ResponseData implements Serializable {
    private static final long serialVersionUID = 1L;

    // 状态码 0 成功  -1 失败
    private int code;
    // 结果
    private Object data;
    // 消息
    private String msg;

    public ResponseData(int code, Object data) {
        this.code = code;
        this.data = data;
    }

    public ResponseData(int code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

 

5.2 实现上传大小限制

在项目入口文件中添加:

@Bean
public MultipartConfigElement multipartConfigElement() {
	MultipartConfigFactory factory = new MultipartConfigFactory();
	// 设置单个文件最大
	factory.setMaxFileSize(DataSize.parse("1024KB"));
	// 设置总上传数据总大小
	factory.setMaxRequestSize(DataSize.parse("10240KB"));
	return factory.createMultipartConfig();
}

 

5.3 部署成jar包,并指定上传目录

5.3.1 增加 jar 包的 Maven 依赖

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

5.3.2 增加配置文件,指定上传路径

在 application.properties 中增加:

web.upload-path=E:/study/Java/upload
spring.resources.static-locations = classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:{web.upload-path}/

修改 fileController.java 文件:

private static final String filePath = "E:/study/Java/upload/";

打包成 jar 包,并运行:

// 使用 maven install
// 生成 jar 包后,运行 jar 包
java -jar xxxxx.jar

 

5.4 性能优化

若是要快速开发,可以使用nginx来配置文件服务器。若要有更好的性能,可以使用 fastdfs 或 oss 来进行搭建。

 

六、打包后,使用分离的配置文件


在实际环境中,可能需要修改配置文件,如果将配置文件打包在项目代码里,则每次修改配置文件都要重新上传项目,这无疑是很麻烦和易错的。

可以将配置文件和代码分离开,实现直接修改配置文件,重启项目即可。只需要在运行 java -jar 时,加入

-Dspring.config.location=/usr/local/proj/application.properties

修改完毕后重启即可。

# 获取当前运行路径
CRTDIR=$(pwd)
PRONAME="upload-demo.jar"


# 判断是否有upload文件夹,没有则创建,用于存储图片
if [ ! -d "${CRTDIR}/upload" ]; then
  mkdir ${CRTDIR}/upload
fi

# 判断是否有logs文件夹,没有则创建,用于存储日志
if [ ! -d "${CRTDIR}/logs" ]; then
  mkdir ${CRTDIR}/logs
fi

# 判断logs文件夹下是否有upload.log文件,没有则创建,用于记录项目日志
if [ ! -f "${CRTDIR}/logs/upload.log" ]; then
  touch ${CRTDIR}/logs/upload.log
fi

nohup java -jar -Dspring.config.location=${CRTDIR}/application.properties ${CRTDIR}/${PRONAME} > ${CRTDIR}/logs/upload.log &
tail -f ${CRTDIR}/logs/upload.log

 

七、注解自动映射到属性和实体类


7.1 使用 @PropertySource 和 @Value 映射属性

在需要映射属性的类上方增加注解,使用 @PropertySource 指定配置文件名,使用 @Value 映射到具体的属性上

@RestController
@PropertySource({"classpath:application.properties"})
public class fileController {
     
    @Value("${web.file.path}")
    private String filePath;

    .....   
}

 

7.2 通过实体类配置

可以通过 @PropertySource 来指定配置文件,然后通过 @ConfigurationProperties来加载指定前缀的参数,并自动匹配到类的属性中去。

使用样例:

在 application.properties 文件中存在如下配置:

# 测试配置文件注入实体类
test.domain=www.greattom.xyz:8080
test.name=springboot-project

创建配置实体类ServerSettings:

package tom.spring.hellospring.domain;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

@Component
@PropertySource({"classpath:application.properties"})
@ConfigurationProperties(prefix="test")
public class ServerSettings {
    private String name;
    private String domain;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }
}

这样配置文件中的 name 属性和 domain 属性就自动匹配到属性中去了。

在控制器中使用:


@RestController
public class GetController {
    ....

    @Autowired
    private ServerSettings serverSettings;

    @GetMapping("v1/test_properties")
    public Object testProperties() {
        return serverSettings;
    }
}

 

八、全局异常


使用 @RestControllerAdvice 捕获全局异常,并用 @ExceptionHandler 指定异常的处理方法。


@RestControllerAdvice
public class CustomEtHandler {
    // 捕获全局异常
    @ExceptionHandler(value=Exception.class)
    Object handlerException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 100);
        map.put("msg", e.getMessage());
        map.put("url", request.getRequestURL());
        return map;
    }

    // 捕获自定义异常
    @ExceptionHandler(value=MyException.class)
    Object handleMyException(MyException e, HttpServletRequest request) {
//        返回指定界面
//        ModelAndView modelAndView = new ModelAndView();
//        modelAndView.setViewName("error.html");
//        modelAndView.addObject("msg", e.getMessage());
//        return modelAndView;

        Map<String, Object> map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("msg", e.getMsg());
        map.put("url", request.getRequestURL());
        return map;
    }
}

若是返回指定界面,则需要加入 Thymekeaf 的 maven 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

 

九、将项目打包成 war 包,并在 tomcat 运行


9.1 打包

1) 指定打包方式

<packaging>war</packaging>

2)指定项目名称,名称可自定义

<finalName>xxxx项目名称</finalName>

3)修改启动类,使用 SpringBootServletInitializer 实现初始化

public class HelloSpringApplication extends SpringBootServletInitializer {

	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		return application.sources(HelloSpringApplication.class);
	}

	public static void main(String[] args) {
		SpringApplication.run(HelloSpringApplication.class, args);
	}
}

4)maven install 生成 war 包

 

9.2 tomcat 下运行

将 war 包放置到 tomcat 的 webapp 文件夹下,启动 tomcat

 

十、过滤器 Filter、自定义Servlet、监听器Listener


10.1 Filter

通过 @WebFilter 来定义这个类为过滤器,并使用 urlPatterns 来指定过滤器的管控路径,使用 filterName 定义过滤器名称。


@WebFilter(urlPatterns = "/api/*", filterName = "loginFilter")
public class LoginFilter implements Filter {

    /*
    * 容器调用的时候
    */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init loginFilter");
    }

    /*
    * 容器被销毁时调用
    */
    @Override
    public void destroy() {
        System.out.println("Destory loginFilter");
    }

    /*
     * 请求拦截的时候调用
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("doFilter loginFilter");
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        String username = req.getParameter("username");
        if ("tom".equals(username)) {
            filterChain.doFilter(servletRequest, servletResponse);
        } else {
            resp.sendRedirect("/index.html");
            return;
        }
    }
}

同时,在启动类中增加:@ServletComponentScan,这个注解会扫描所有的 @WebFilter、@WebListener、@WebListener 并自动注册。

 

10.2 自定义 Servlet

Servlet 可以认为是接口,但在框架中使用 @RequestMapping 等注解则是自动帮我们完成了编写和注册 Servlet 的操作。故使用框架更为方便。


@WebServlet(name = "userServlet", urlPatterns = "/v1/test/customs")
public class UserServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().print("custom sevlet");
        resp.getWriter().flush();
        resp.getWriter().close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

 

10.3 监听器 Listener

通过 @WebListener 来注册监听器

@WebListener
public class RequestListener implements ServletRequestListener {
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("========== RequestDestoryed =========");
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("========== RequestInit =========");
    }
}

 

10.4 拦截器

拦截器需要编写配置类,并使用 @Component 注解来让编译器扫描它。多个拦截器时,拦截器执行顺序会根据注册顺序拦截。

// 注册类
@Configuration
public class CustomWebMvcConf implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 调用时依照注册顺序调用
        registry.addInterceptor(new LoginIntercepter()).addPathPatterns("/api2/*/**");
        registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api2/**");
        // .registry.excludePathPatterns("/api2/register") // 排除注册的路径
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}
// 拦截器一

public class LoginIntercepter implements HandlerInterceptor {

    /*
    *  调用方法之前
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginIntercepter preHandle");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    /*
    *  调用方法之后,视图渲染之前
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("LoginIntercepter postHandle");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    /*
    *  方法调用完成之后
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("LoginIntercepter afterHandle");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
// 拦截器二

public class TwoIntercepter implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("TwoInterceptor preHander");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("TwoInterceptor postHander");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("TwoInterceptor afterHander");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

 

10.4 调用顺序

可以看到,调用顺序为: 过滤器 => 拦截器 => 逻辑代码 => 拦截器

 

 

10.5 过滤器和拦截器的区别和使用

 过滤器 Filter拦截器 Interceptor
实现原理基于函数回调 doFilter()基于 AOP 思想(Java 反射)
生命周期只在函数调用前后起作用方法前后,异常抛出前后等
依赖环境依赖于 Servlet 容器中不依赖 Servlet 容器
调用次数整个 Action 中只能被调用一次可以被多次调用
触发范围可以拦截几乎所有请求(Action 请求和 静态资源请求)只能拦截 Action 请求

调用顺序为:

     过滤前-拦截前-action执行-拦截后-过滤后

 

十一、MyBatis 使用


11.1 什么是 Mybatis

MyBatis 是用于映射 java 实体类和数据库的,基于JAVA的半ORM持久层框架。

 

11.2 Mybatis 的使用 -- 使用 java 方式

11.2.1 在启动入口类上添加mapper处理路径

@MapperScan("tom.spring.hellospring.mapper")

  

11.2 增加配置文件

#可以自动识别
#spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.47.135:3306/mybatisDemo?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =root
spring.datasource.password =Tom@123456
#如果不使用默认的数据源 (com.zaxxer.hikari.HikariDataSource)
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource

#开启控制台打印sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

11.3 构建项目目录

11.4 增加文件

// UserService.java
/*
* service 层定义接口,具体实现由impl完成
*/
public interface UserService {
    public Object addUser(UserEntity userEntity);
    public Object delUser(int id);
    public Object updateUser(UserEntity userEntity);
    public Object findById(int id);
    public List<UserEntity> getAll();
}
// UserServiceImpl.java


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public Object addUser(UserEntity userEntity) {
        userMapper.save(userEntity);
        return JsonData.buildSuccess(userEntity);
    }

    @Override
    public Object delUser(int id) {
        userMapper.remove(id);
        return JsonData.buildSuccess();
    }

    @Override
    public Object updateUser(UserEntity userEntity) {
        userMapper.update(userEntity);
        return JsonData.buildSuccess();
    }

    @Override
    public UserEntity findById(int id) {
        return userMapper.findById(id);
    }

    @Override
    public List<UserEntity> getAll() {
        return userMapper.getAll();
    }
}
// UserMapper.java

@Mapper
@Repository
public interface UserMapper extends BasicMapper<UserEntity> {

    @Override
    @Insert("INSERT INTO user(name, phone, create_time, age) VALUES(#{name}, #{phone}, #{createTime}, #{age})")
    @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
    int save(UserEntity userEntity);

    @Override
    @Update("UPDATE user SET name=#{name} WHERE id=#{id}")
    int update(UserEntity userEntity);

    @Override
    @Delete("DELETE FROM user WHERE id=#{id}")
    int remove(Object id);

    @Override
    @Select("SELECT * FROM user WHERE id=#{id}")
    @Results(
        @Result(column = "create_time", property = "createTime")
    )
    UserEntity findById(Object id);

    @Override
    @Select("SELECT * FROM user")
    @Results(
            @Result(column = "create_time", property = "createTime")
    )
    List<UserEntity> getAll();
}

 

11.3 Mybatis 的使用 -- 使用 xml 方式

11.3.1  增加项目文件

// StudentMapper.java

@Mapper
@Repository
public interface StudentMapper extends BasicMapper<StudentEntity> {
}
// StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="tom.spring.hellospring.mapper.StudentMapper">

<!--    <resultMap type="tom.spring.hellospring.entity.StudentEntity" id="StudentEntityResult">-->
<!--        <id property="id" column="id"  />-->
<!--        <result property="createTime" column="create_time" />-->
<!--    </resultMap>-->

    <insert id="save" useGeneratedKeys="true" keyProperty="id" keyColumn="id" >
        INSERT INTO student (
            `name`,
            `phone`,
            `create_time`,
            `age`
        )
        VALUES (
            #{name},
            #{phone},
            #{create_time},
            #{age}
        )
    </insert>

    <delete id="remove">
        DELETE FROM
            student
        WHERE
            id = #{id}
    </delete>

    <select id="findById" resultType="tom.spring.hellospring.entity.StudentEntity">
        SELECT
            *
        FROM
            student
        WHERE
            id = #{id}
    </select>

    <select id="getAll" resultType="tom.spring.hellospring.entity.StudentEntity">
        SELECT
            *
        FROM
            student
    </select>
</mapper>
// StudentService.java

public interface StudentService {

    public Object addStu(StudentEntity stu);

    public List<StudentEntity> getAll();
}
// StudentServiceImpl.java

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    public Object addStu(StudentEntity stu) {
        studentMapper.save(stu);
        return JsonData.buildSuccess(stu);
    }

    public List<StudentEntity> getAll() {
        return studentMapper.getAll();
    }
}
// StudentEntity.java

public class StudentEntity {
    private int id;
    private String name;
    private String phone;
    public int age;
    private Date create_time;

    public StudentEntity() {
    }

    public StudentEntity(int id, String name, String phone, int age, Date create_time) {
        this.id = id;
        this.name = name;
        this.phone = phone;
        this.age = age;
        this.create_time = create_time;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getCreate_time() {
        return create_time;
    }

    public void setCreate_time(Date create_time) {
        this.create_time = create_time;
    }
}
// StudentController.java


@RestController
@RequestMapping("/stu")
public class StudentController {

    @Autowired
    StudentService stu;

    @GetMapping("add")
    public Object add_stu() {
        StudentEntity stuEnty = new StudentEntity();
        stuEnty.setName("xiaoming");
        stuEnty.setAge(18);
        stuEnty.setCreate_time(new Date());
        stuEnty.setPhone("10099");
        return stu.addStu(stuEnty);
    }

    @GetMapping("getAll")
    public Object get_all() {
        return stu.getAll();
    }
}

 

11.3.2  修改配置文件

在 pom.xml 文件中添加:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
		
    ......

	<build>
		<resources>
			<resource>
				<directory>src/main/java</directory>
				<includes>
					<include>**/*.xml</include>
				</includes>
			</resource>
		</resources>
	</build>
</project>

在 application.properties 中增加:

mapper-locations 指向放置 xml 文件的路径

type-aliases-package 指向实体类的文件夹

spring.datasource.driver-class-name =com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://1.1.1.1:3306/mybatisDemo?useUnicode=true&characterEncoding=utf-8
spring.datasource.username =xxxx
spring.datasource.password =xxxx
spring.datasource.type =com.alibaba.druid.pool.DruidDataSource


mybatis.mapper-locations:classpath:tom/spring/hellospring/mapper/*.xml
mybatis.type-aliases-package=tom.spring.hellospring.entity

 

11.4 Mybatis 的使用 -- 事务

在 impl 类中的实现方法中使用注解 @Transactional

@Override
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
//    isolation 隔离级别 propagation 传播行为
public int addCount(UserEntity userEntity) {
    userMapper.save(userEntity);
    // 模拟异常
    int i = 19/0;
    return 0;
}

 

十二、Redis 使用


12.1 安装和配置

12.1.1 安装

网盘下载:

链接:https://pan.baidu.com/s/1mingLTtX0hmIWeOd2-dOQw 
提取码:tkmu

官网下载:

https://redis.io/download

在 /usr/local 目录下:

wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar xzf redis-5.0.7.tar.gz
mv redis-5.0.7 redis
cd redis
make

测试是否成功:

./src/redis-server

  

 

 

12.1.2 设置后台启动

将 redis.conf 文件中的 daemonize 从 no 改为 yes,重启 redis

 

12.1.3 配置脚本:开启和关闭

 脚本 - 启动 redis:start.sh

#! /bin/bash
/usr/local/redis/src/redis-server  /usr/local/redis/redis.conf

脚本 - 关闭 redis: stop.sh

#! /bin/bash
/usr/local/redis/src/redis-cli shutdown

脚本 - 进入客户端: client.sh

#! /bin/bash
./src/redis-cli

 

12.1.4 设置远程访问权限(正式环境记得关掉)

将 redis.conf 文件中的 bind 字段注释掉,或将 bind 127.0.0.1 改为 bind 0.0.0.0

将 protected-mode 从 yes 改为 no

protected-mode 当为yes时,禁止外网访问,生产环境应设为yes

 

12.1.5 修改默认端口6379

将 redis.conf 文件中的 port 6379 改为要分配的端口

 

12.1.6 设置和修改密码

redis 初始时是没有密码的,为了安全性需要对 redis 设置密码

进入 redis 后,配置密码:

config set requirepass 123456

查看:info(验证无法通过)

授权登陆  auth 123456

 

12.2 使用

在 https://docs.spring.io/spring-boot/docs/2.2.4.RELEASE/reference/htmlsingle/ 中可以看到maven: 

在 pom.xml 中引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

使用:

@RestController
@RequestMapping("/redis")
public class RedisController {
    @Autowired
    private StringRedisTemplate redisTpl;

    @GetMapping(value = "get")
    public Object get() {
        String value = redisTpl.opsForValue().get("name");
        return JsonData.buildSuccess(value);
    }

    @GetMapping(value = "add")
    public Object add() {
        redisTpl.opsForValue().set("name", "tom");
        return JsonData.buildSuccess("ok");
    }
}

 

12.3 可视化工具 Redis Desktop Manager

官网: https://redisdesktop.com/

网盘链接:https://pan.baidu.com/s/1T8_HbJ3GLRDw9A2YtOgdoQ  提取码:juhw 

 

12.4. 封装

redisClient.java :

@Component
public class RedisClient {

	@Autowired
	private StringRedisTemplate redisTpl;

	public boolean set(String key, String value) {
		try {
			redisTpl.opsForValue().set(key, value);
			return true;
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public String get(String key) {
		return key==null?null:redisTpl.opsForValue().get(key);
	}

	public boolean remove(String key) {
		return key==null?null:redisTpl.delete(key);
	}


	/**
	 * 功能描述:设置某个key过期时间
	 * @param key
	 * @param time
	 * @return
	 */
	  public boolean expire(String key,long time){
	        try {
	            if(time>0){
					redisTpl.expire(key, time, TimeUnit.SECONDS);
//					redisTpl.expire(key, time, TimeUnit.MILLISECONDS);
	            }
	            return true;
	        } catch (Exception e) {
	            e.printStackTrace();
	            return false;
	        }
	    }

	  /**
	   * 功能描述:根据key 获取过期时间
	   * @param key
	   * @return
	   */
	  public long getExpire(String key){
	        return redisTpl.getExpire(key,TimeUnit.SECONDS);
	    }


	  	/**
	     * 递增
	     * @param key 键
	     * @return
	     */
	    public long incr(String key, long delta){
	        return redisTpl.opsForValue().increment(key, delta);
	    }


	    /**
	     * 递减
	     * @param key 键
	     * @param delta 要减少几
	     * @return
	     */
	    public long decr(String key, long delta){
	        return redisTpl.opsForValue().increment(key, -delta);
	    }
}

工具类 JsonUtils.java :


public class JsonUtils {

    private static ObjectMapper objectMapper = new ObjectMapper();

    //对象转字符串
    public static <T> String obj2String(T obj){
        if (obj == null){
            return null;
        }
        try {
            return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    //字符串转对象
    public static <T> T string2Obj(String str,Class<T> clazz){
        if (StringUtils.isEmpty(str) || clazz == null){
            return null;
        }
        try {
            return clazz.equals(String.class)? (T) str :objectMapper.readValue(str,clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

测试 RedisController.java :


@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("add")
    public Object addUser() {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("xiaoming");
        userEntity.setPhone("10086");
        userEntity.setAge(18);
        userEntity.setCreateTime(new Date());
        userService.addUser(userEntity);
        return JsonData.buildSuccess(userEntity);
    }

    @GetMapping("del")
    public Object delUser(int id) {
        userService.delUser(id);
        return JsonData.buildSuccess();
    }

    @GetMapping("update")
    public Object updateUser(String name, int id) {
        UserEntity userEntity = new UserEntity();
        userEntity.setName(name);
        userEntity.setId(id);
        userService.updateUser(userEntity);
        return JsonData.buildSuccess();
    }

    @GetMapping("findById")
    public Object findById(int id) {
        return JsonData.buildSuccess(userService.findById(id));
    }

    @GetMapping("getAll")
    public Object getAll() {
        return JsonData.buildSuccess(userService.getAll());
    }

    @GetMapping("addCount")
    public Object addCount() {
        UserEntity userEntity = new UserEntity();
        userEntity.setName("laowang");
        userEntity.setPhone("111");
        userEntity.setAge(29);
        userEntity.setCreateTime(new Date());
        userService.addCount(userEntity);
        return JsonData.buildSuccess(userEntity);
    }
}

 

十三、Schedule 定时任务


使用定时任务,例如每天凌晨统计数据,每月统计数据等。

13.1 几种定时任务方式

1、常见定时任务 Java自带的java.util.Timer类
            timer:配置比较麻烦,时间延后问题
            timertask:不推荐

2、Quartz框架
            配置更简单
            xml或者注解

3、SpringBoot使用注解方式开启定时任务
            1)启动类里面 @EnableScheduling开启定时任务,自动扫描
            2)定时任务业务类 加注解 @Component被容器扫描
            3)定时执行的方法加上注解 @Scheduled(fixedRate=2000) 定期执行一次

 

13.2 使用

13.2.1 配置和类

启动类中添加:

@EnableScheduling

新建 task 包,新建 TestTask.java 

@Component
public class TestTask {
    @Scheduled(fixedRate = 2000)
    public void sum() {
        System.out.println("当前时间: " + new Date());
    }
}

13.2.2 常用的定时任务表达式

1、cron 定时任务表达式 @Scheduled(cron="*/1 * * * * *") 表示每秒
            1)crontab 工具  https://tool.lu/crontab/
2、fixedRate: 定时多久执行一次(上一次开始执行时间点后xx秒再次执行;)
3、fixedDelay: 上一次执行结束时间点后xx秒再次执行
4、fixedDelayString:  字符串形式,可以通过配置文件指定

crontab 参考链接:https://www.cnblogs.com/javahr/p/8318728.html

 

十四、异步任务


当用户上传一个表格时,后端服务器需要解析数据,并存到数据库。

若是同步,则用户调用上传接口后,需要等待: 上传文件 => 解析数据 => 存到数据库 => 服务器返回上传成功,这时页面会一直卡住

若是异步,则用户调用上传接口后,只要等待: 上传文件  => 服务器返回上传成功 => 服务器解析数据 => 存到数据库,这时页面在上传文件成功后,即可操作。速度无疑快了很多

 

14.1 异步任务的几种方式

 

14.2 springboot 异步执行的使用

1)在启动类上增加 @EnableAsync , 表示扫描异步组件

@EnableAsync

2)异步任务需要封装到类中,不能直接写在 controller 中。若要监控是否完成, 增加Future<String> 返回结果 AsyncResult<String>("task执行完成")。

@Component
public class AsyncTask {

    @Async
    public void task1() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(1000L);
        long end = System.currentTimeMillis();
        System.out.println("校验 耗时 = " + (end - begin));
    }

    @Async
    public void task2() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(2000L);
        long end = System.currentTimeMillis();
        System.out.println("发邮件 耗时 = " + (end - begin));
    }

    @Async
    public void task3() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("保存用户信息 耗时 = " + (end - begin));
    }

    // 获取异步结果
    @Async
    public Future<String> task4() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("任务4 耗时 = " + (end - begin));
        return new AsyncResult<String>("任务4");
    }

    // 获取异步结果
    @Async
    public Future<String> task5() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("任务5 耗时 = " + (end - begin));
        return new AsyncResult<String>("任务5");
    }

    // 获取异步结果
    @Async
    public Future<String> task6() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread.sleep(3000L);
        long end = System.currentTimeMillis();
        System.out.println("任务6 耗时 = " + (end - begin));
        return new AsyncResult<String>("任务6");
    }
}

3)controller 中使用


@RestController
@RequestMapping("/async")
public class AsyncController {

    @Autowired
    private AsyncTask task;

    @GetMapping("task")
    public JsonData exeTask() throws InterruptedException {
        long begin  = System.currentTimeMillis();
//        task.task1();
//        task.task2();
//        task.task3();

        Future<String> task4 = task.task4();
        Future<String> task5 = task.task5();
        Future<String> task6 = task.task6();

        for (;;) {
            if (task4.isDone() && task5.isDone() && task6.isDone()) {
                break;
            }
        }

        long end  = System.currentTimeMillis();
        System.out.println("执行总耗时 = " + (end-begin));
        return JsonData.buildSuccess((end-begin));
    }
}

 

十五、使用日志框架


15.1 常用的日志框架

  • slf4j
  • log4j
  • logback
  • common-logging

15.2 logback 使用

15.2.1 logback 介绍

基于 Log4j 的基础上大量改良,不能单独使用,推荐配合SLF4J使用

 

15.2.2 logback 核心对象

logger: 日志记录器

Appender: 指定日志输出的目的地,目的地可以是控制台,也可以是文件

Layout: 日志布局,格式化日志信息的输出

 

15.2.3 日志级别

DEBUG < INFO < WARN < ERROR

 

15.2.4 logback 使用

1) 使用 -spring.xml 结尾,便于spring扫描到

       默认加载加载配置顺序 logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy

2) 填写 logback-spring.xml

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!--  name: 自定义的名称   class: logback提供的类  -->
    <appender name="consoleApp" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </layout>
    </appender>

    <appender name="fileInfoApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>ACCEPT</onMismatch>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>
        <!-- 滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>app_log/log/app.info.%d.log</fileNamePattern>
        </rollingPolicy>
    </appender>

    <appender name="fileErrorApp" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>
                %date{yyyy-MM-dd HH:mm:ss.SSS} %-5level[%thread]%logger{56}.%method:%L -%msg%n
            </pattern>
        </encoder>

        <!-- 设置滚动策略 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 路径 -->
            <fileNamePattern>app_log/log/app.err.%d.log</fileNamePattern>

            <!-- 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,
            且<maxHistory> 是1,则只保存最近1个月的文件,删除之前的旧文件 -->
            <MaxHistory>1</MaxHistory>

        </rollingPolicy>
    </appender>

    <!--  控制总的输出级别  -->
    <root level="WARN">
        <!-- 指定输出目的 -->
        <appender-ref ref="consoleApp"/>
        <appender-ref ref="fileInfoApp"/>
        <appender-ref ref="fileErrorApp"/>
    </root>
</configuration>

 

十六、搜索框架 ElastcSeacrh 的使用


16.1 ElastcSearch 简介

16.1.1 ElastcSearch 做什么

ElastcSearch 用于对处理面对海量数据的搜索,如京东商城,淘宝搜索等

16.1.2 特点

  • 全文检索,结构化检索,数据统计、分析,接近实时处理,分布式搜索(可部署数百台服务器),处理PB级别的数据搜索纠错,自动完成
  • 使用场景:日志搜索,数据聚合,数据监控,报表统计分析
  • 国内外使用者:维基百科,Stack Overflow,GitHub

 

16.2 ElastcSearch 使用

1)需要使用 jdk 1.8,下载压缩包并解压

# 下载压缩包
wget  https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.8.tar.gz

# 解压
tar -zxvf elasticsearch-5.6.8.tar.gz

# 确定 java 版本 (应为 1.8)
java -version

  

2)使用

./bin/elasticsearch

  

 若碰到错误或警告,请看:elasticsearch 报错问题汇总

3)RESTAPI

(请求方法  RESTAPI)

# 查看集群状态
GET /_cat/health?v

# 查看所有node
GET /_cat/nodes?v

# 查看所有索引
GET /_cat/indices?v

# 创建索引
PUT /customer?pretty

# 加入数据
# customers对应数据库, external对应表,展示为 _type  1为id
PUT /customer/external/1?pretty
在 body 中加入: {"name": "John Doe"}

# 获取数据
GET /customer/external/1?pretty

 

16.3 springboot 整合 elasticsearch

16.3.1 加入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

 

16.3.2 增加配置

在 application.properties 文件中增加:

spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.47.135:9300
spring.data.elasticsearch.repositories.enabled=true

 

16.3.3 相关代码

ArticleRepository.java

@Component
public interface ArticleRepository extends ElasticsearchRepository<ArticleEntity, Long> {

}

 

ArticleEntity.java

// 存到 blog 数据库, artical 表
@Document(indexName= "blog", type="artical")
public class ArticleEntity {

    private static final long serialVersionUID = 1L;

    private long id;

    private String title;

    private String summary;

    private String content;

    private int pv;

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSummary() {
        return summary;
    }

    public void setSummary(String summary) {
        this.summary = summary;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getPv() {
        return pv;
    }

    public void setPv(int pv) {
        this.pv = pv;
    }
}

 

ArticleController.java

@RestController
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private ArticleRepository articleRepository;

    @GetMapping("save")
    public Object save() {
        ArticleEntity articleEntity = new ArticleEntity();
        articleEntity.setId(5L);
        articleEntity.setPv(888);
        articleEntity.setContent("this is content");
        articleEntity.setTitle("springboot");
        articleEntity.setSummary("概要");
        articleRepository.save(articleEntity);

        return JsonData.buildSuccess();
    }

    @GetMapping("search")
    public Object search(String title) {
        QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", title);
        Iterable<ArticleEntity> list = articleRepository.search(queryBuilder);
        return JsonData.buildSuccess(list);
    }
}

 

16.3.4 使用

存储:

http://localhost:8095/article/save

检查:

访问服务器上的 elasticsearch 端口,查看对应的索引和类型

http://192.168.47.135:9200/blog/artical/2

 

调用接口查询:

使用了 QueryBuilder 查询相关数据

http://localhost:8095/article/search?title=springboot

 

十七、ActiveMQ 和 RocketMQ 的使用


17.1 消息队列简洁

17.1.1 消息队列的作用

  • 跨平台 
  • 多语言 
  • 多项目
  • 解耦
  • 分布式事务
  • 流量控制
  • 最终一致性
  • RPC调用

17.1.2 消息队列内容

  • JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
  • JMS生产者(Message Producer)
  • JMS消费者(Message Consumer)
  • JMS消息
  • JMS队列
  • JMS主题

 

17.2 ActiveMQ 的使用

17.2.1 ActiveMQ 安装

1)下载安装包

官网下载:http://activemq.apache.org/activemq-5153-release.html

使用手册:http://activemq.apache.org/getting-started.html

百度网盘下载:

链接:https://pan.baidu.com/s/1qH4mozanAteOyCDIvNVWcw 
提取码:t5s7

2)解压安装包

tar -zxvf apache-activemq-5.15.3-bin.tar

3)启动activemq服务

cd /usr/local/apache-activemq-5.15.3/bin
./activemq start

4)开放端口

网页访问端口:8161

JAVA 服务访问端口: 61616

# 打开对应端口
firewall-cmd --zone=public --add-port=61616/tcp --permanent
firewall-cmd --zone=public --add-port=8161/tcp --permanent

# 重启防火墙
firewall-cmd --reload

5)访问控制页面,端口为 8161,账号密码默认为 admin admin

# http://xxxxxx:8161
http://192.168.47.135:8161

 

17.2.2 增加依赖

不同的 springboot 版本,增加的依赖不同: 配置连接池,启动项目报错

<!-- 整合消息队列ActiveMQ -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

<!-- activemq配置线程池则加入 -->
<!-- springboot2.0+及以下版本 -->
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-pool</artifactId>
</dependency>

<!-- springboot2.1+ -->
<dependency>
    <groupId>org.messaginghub</groupId>
    <artifactId>pooled-jms</artifactId>
</dependency>

17.2.3 增加 application.properties 配置

######### activemq 配置 #########
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://192.168.47.135:61616

#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin

#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100

#开启订阅模式
#default point to point
spring.jms.pub-sub-domain=true

17.2.4 使用

1)启动类中添加 @EnableJms

@EnableJms // 开启JMS
public class DemoApplication {
    .....

	@Bean // 交给spring管理,方便后续进行注入
	public Queue queue() {
		return new ActiveMQQueue("article.queue");
	}

	@Bean
	public Topic topic() {
		return new ActiveMQTopic("news.topic");
	}

    // 若要同时支持点对点模式和发布订阅模式,需要给topic定义独立的JmsListenerContainer
	@Bean
	public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
		DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
		bean.setPubSubDomain(true);
		bean.setConnectionFactory(activeMQConnectionFactory);
		return bean;
	}
}

2)编写生产者:

// ProducerService.java

public interface ProducerService {

    /**
     * 功能描述:指定消息队列,还有消息
     * @param destination
     * @param message
     */
    public void sendMessage(Destination destination, final String message);

    /**
     * 功能描述:使用默认消息队列, 发送消息
     * @param message
     */
    public void sendMessage( final String message);


    /**
     * 功能描述:消息发布者
     * @param msg
     */
    public void publish(String msg);
}
// ProducerServiceImpl.java

/**
 * 功能描述:消息生产者
 */
@Service
public class ProducerServiceImpl implements ProducerService {

    @Autowired
    private Queue queue;

    @Autowired
    private JmsMessagingTemplate jmsTemplate; //用来发送消息到broker的对象

    //发送消息,destination是发送到的队列,message是待发送的消息
    @Override
    public void sendMessage(Destination destination, String message) {
        jmsTemplate.convertAndSend(destination, message);
    }


    //发送消息,destination是发送到的队列,message是待发送的消息
    @Override
    public void sendMessage(final String message) {
        jmsTemplate.convertAndSend(this.queue, message);
    }

    //=======发布订阅相关代码=========

    @Autowired
    private Topic topic;

    @Override
    public void publish(String msg) {
        this.jmsTemplate.convertAndSend(this.topic, msg);
    }
}
// OrderController.java

@RestController
@RequestMapping("/mq")
public class OrderController {
    @Autowired
    private ProducerService producerService;
 
    @GetMapping("order")
    public Object order(String msg){
        Destination destination = new ActiveMQQueue("order.queue");
        producerService.sendMessage(destination, msg);
        return JsonData.buildSuccess();
    }

    @GetMapping("common")
    public Object common(String msg){
        producerService.sendMessage(msg);
        return JsonData.buildSuccess();
    }

    @GetMapping("publish")
    public Object topic(String msg) {
        producerService.publish(msg);
        return JsonData.buildSuccess();
    }
}

3)编写消费者

// OrderConsumer.java

@Component
// 监听是否有消息到来
public class OrderConsumer {

    @JmsListener(destination = "order.queue")
    public void receiveQueue(String text) {
        System.out.println("OrderConsumer 收到的报文为: " + text);
    }
}

4)编写订阅者

// TopicSub.java

@Component
public class TopicSub {

    @JmsListener(destination = "news.topic")
    public void receive(String text) {
        System.out.println("news.topic 消费者: reveive = " + text);
    }

    @JmsListener(destination = "news.topic")
    public void receive2(String text) {
        System.out.println("news.topic 消费者: reveive2 = " + text);
    }

    @JmsListener(destination = "news.topic")
    public void receive3(String text) {
        System.out.println("news.topic 消费者: reveive3 = " + text);
    }
}

5)当同时使用 点对点 模式和 发布/订阅 模式时,仅有 发布/订阅 模式有效,需要使用 JMS 工厂:

修改配置文件:

# application.properties


######### activemq 配置 #########

#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://192.168.47.135:61616

#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin

#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100

#开启订阅模式, 若要同时兼容p2p和pub/sub模式,则要注释掉
#default point to point
#spring.jms.pub-sub-domain=true

添加 JmsListenerContainerFactory:

@EnableJms // 开启JMS
public class DemoSpringApplication {

	public static void main(String[] args) {
		SpringApplication.run(HelloSpringApplication.class, args);
	}

	@Bean // 交给spring管理,方便后续进行注入
	public Queue queue() {
		return new ActiveMQQueue("article.queue");
	}

	@Bean
	public Topic topic() {
		return new ActiveMQTopic("news.topic");
	}

	// 若要同时支持点对点模式和发布订阅模式,需要给topic定义独立的JmsListenerContainer
	@Bean
	public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
		DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
		bean.setPubSubDomain(true);
		bean.setConnectionFactory(activeMQConnectionFactory);
		return bean;
	}
}

修改订阅代码, 增加: containerFactory = "jmsListenerContainerTopic"

// TopicSub.java

@Component
public class TopicSub {

    @JmsListener(destination = "news.topic", containerFactory = "jmsListenerContainerTopic")
    public void receive(String text) {
        System.out.println("news.topic 消费者: reveive = " + text);
    }

    @JmsListener(destination = "news.topic", containerFactory = "jmsListenerContainerTopic")
    public void receive2(String text) {
        System.out.println("news.topic 消费者: reveive2 = " + text);
    }

    @JmsListener(destination = "news.topic")
    public void receive3(String text) {
        System.out.println("news.topic 消费者: reveive3 = " + text);
    }
}

 

17.3 RocketMQ 的使用

17.3.1 RocketMQ 简洁

Apache RocketMQ作为阿里开源的一款高性能、高吞吐量的分布式消息中间件。

淘宝 --- 十分钟入门RocketMQ

 

17.3.2  RocketMQ 特点

  • 在高压下1毫秒内响应延迟超过99.6%。
  • 适合金融类业务,高可用性跟踪和审计功能。
  • 支持发布订阅模型,和点对点
  • 支持拉pull和推push两种消息模式
  • 单一队列百万消息
  • 支持单master节点,多master节点,多master多slave节点

 

17.3.3 RocketMQ 概念

  • Producer:消息生产者
  • Producer Group:消息生产者组,发送同类消息的一个消息生产组
  • Consumer:消费者
  • Consumer Group:消费同个消息的多个实例
  • Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息
  • Topic:主题
  • Message:消息
  • Broker:MQ程序,接收生产的消息,提供给消费者消费的程序
  • Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由    

 

17.3.4 RocketMQ 环境搭建

1)安装

官网下载:https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.2.0/rocketmq-all-4.2.0-bin-release.zip

使用手册:http://rocketmq.apache.org/docs/quick-start/

百度网盘:

链接:https://pan.baidu.com/s/1fB79eZolYc_Ynh4u1OrdyQ 
提取码:0khs

 

2)解压:

# 创建 rocketmq 文件夹
mkdir rocketmq

# 解压压缩包到 rocketmq 文件夹下
unzip rocketmq-all-4.2.0-bin-release.zip ./rocketmq

 

3)启动

启动 namesrv 服务

cd /rocketmq/bin

# 启动 namesrv
nohup sh mqnamesrv & 

查看日志文件 nohup.out

tail -f nohup.out

启动成功  

  

启动 broker 服务

nohup sh mqbroker -n 127.0.0.1:9876 &

查看日志文件 nohup.out

tail -f nohup.out

查看服务是否正确开启

jps

  

 

4)可能遇到的问题(内存分配)

开启时,你可能在 nohup.out 文件中看到这样的错误提示: Cannot allocate memory (无法分配足够的内存)

  

通过修改配置文件方式:

参数说明:

  • Xms:  为jvm启动时分配的内存,比如-Xms200m,表示分配200M
  • Xmx:  为jvm运行过程中分配的最大内存,比如-Xmx500m,表示jvm进程最多只能够占用500M内存
  • Xss:   为jvm启动的每个线程分配的内存大小
  • MetaSpace:  表示metaspace首次使用不够而触发FGC的阈值
  • MaxMetaSpace:  用于设置metaspace区域的最大值

修改 namesrv 服务内存:

vim runserver.sh

  

修改 broker 服务内存

vim runbroker.sh 

  

 

4)如何关闭服务

sh bin/mqshutdown namesrv
sh bin/mqshutdown broker

 

5)控制台搭建

5.1 下载

官网下载:

 

5.2 编译成 jar 文件

mvn clean install '-Dmaven.test.skip=true'

 

 

十八、多环境配置


18.1 为什么要多环境配置

多环境配置可根据当前环境是开发环境(dev)、测试环境(test)、预发布环境(pre)和生产环境(prod),使用不同的配置文件,如链接不同的数据库等。

 

18.2 如何使用

spring.profiles.active=dev

在resources/config文件夹下创建多个配置文件,并指定使用哪个配置文件:

 

十八、响应式编程


18.1 为什么要多环境配置

多环境配置可根据当前环境是开发环境(dev)、测试环境(test)、预发布环境(pre)和生产环境(prod),使用不同的配置文件,如链接不同的数据库等。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值