• SpringBoot 08: SpringBoot综合使用 MyBatis, Dubbo, Redis


    业务背景

    Student表

    CREATE TABLE `student` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
      `phone` varchar(11) COLLATE utf8_bin DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    

    两个业务功能

    针对上述student表, 综合应用springboot, mybatis, dubbo, redis实现如下两个业务功能

    1. 注册学生
    • 要求:

    • 注册接口定义为:int saveStudent(Student student)

    • 利用传入的学生的手机号注册,手机号必须唯一

    • 如果已经存在了手机号, 注册失败, 返回2

    • 如果手机号为空,注册失败,返回-1

    • 注册成功,返回0

    2. 查询学生
    • 要求:
    • 查询接口定义为:Student queryStudent(Integer id)
    • 根据id查询目标学生
    • 先到redis查询学生,如果redis没有,从数据库查询
    • 如果数据库有,把查询到的学生放入到redis,返回该学生,后续再次查询这个学生应该从redis就能获取到
    • 如果数据库也没有目标学生,返回空

    其他要求

    • 关于Dubbo
      • 要求使用dubbo框架,addStudent, queryStudent是由服务提供者实现的
      • 消费者可以是一个Controller,调用提供者的两个方法, 实现学生的注册和查询
    • 关于前端页面
      • 页面使用html, ajax, jquery
      • 通过postman发送post请求,来注册学生
      • 通过html页面上的form表单,提供文本框输入id, 进行查询
      • html, jquery.js都放到springboot项目的resources/static目录中

    编程实现

    项目结构

    • 分布式总体项目结构

    image

    • 公共接口项目结构

    image

    • 服务提供者项目结构

    image

    • 消费者项目结构

    image

    dubbo的公共接口项目

    注意该项目为普通的maven项目即可

    实体类
    package com.example.demo.model;
    
    import java.io.Serializable;
    
    public class Student implements Serializable {
        private static final long serialVersionUID = -3272421320600950226L;
        private Integer id;
        private String name;
        private String phone;
        private Integer age;
    
        @Override
        public String toString() {
            return "Student{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", phone='" + phone + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        //防止缓存穿透,可以获取默认学生(学生信息故意设置不合法,后期在redis中一眼就能看出来是异常数据),填充到redis中
        public static Student getDefaultStudent(){
            Student student = new Student();
            student.setId(-1);
            student.setName("-");
            student.setPhone("-");
            student.setAge(0);
            return student;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer 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 Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Student(Integer id, String name, String phone, Integer age) {
            this.id = id;
            this.name = name;
            this.phone = phone;
            this.age = age;
        }
    
        public Student() {
        }
    }
    
    提供的服务接口定义
    package com.example.demo.service;
    
    import com.example.demo.model.Student;
    
    public interface StudentService {
        //保存学生信息
        int saveStudent(Student student);
    
        //根据id,查询学生信息
        Student queryStudent(Integer id);
    }
    

    dubbo的服务提供者项目

    注意:该项目为springboot项目,且在起步依赖里要勾选web(web依赖可以不选), redis, mysql, mybatis的起步依赖

    项目配置
    • 额外在pom.xml里手动加入对公共接口项目以及dubbo和zookeeper的依赖
            
            <dependency>
                <groupId>com.example.demogroupId>
                <artifactId>demo-apiartifactId>
                <version>1.0.0version>
            dependency>
    
            
            <dependency>
                <groupId>org.apache.dubbogroupId>
                <artifactId>dubbo-spring-boot-starterartifactId>
                <version>2.7.8version>
            dependency>
    
    
            
            <dependency>
                <groupId>org.apache.dubbogroupId>
                <artifactId>dubbo-dependencies-zookeeperartifactId>
                <version>2.7.8version>
                <type>pomtype>
                <exclusions>
                    
                    <exclusion>
                        <artifactId>slf4j-log4j12artifactId>
                        <groupId>org.slf4jgroupId>
                    exclusion>
                exclusions>
            dependency>
    
    • 配置application.properties文件
    ########################### 配置dubbo
    #配置提供的服务名称
    dubbo.application.name=student-service-provider
    
    #配置需要扫描的包
    dubbo.scan.base-packages=com.example.demo.service
    
    #配置注册中心
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
    ########################### 配置redis
    #redis服务的ip
    spring.redis.host=127.0.0.1
    
    #redis服务的端口
    spring.redis.port=6379
    
    ########################### mybatis配置
    #mybatis中mapper文件编译到的资源路径
    mybatis.mapper-locations=classpath:mapper/*.xml
    
    #mybatis日志输出
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
    ############################ 数据源配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://数据库服务器ip:3306/数据库名?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
    spring.datasource.username=XXX
    spring.datasource.password=YYY
    
    dao层
    • dao接口
    package com.example.demo.dao;
    
    import com.example.demo.model.Student;
    import org.apache.ibatis.annotations.Param;
    
    public interface StudentDao {
        //以手机号作为查询条件,判断学生是否存在
        Student queryStudentByPhone(@Param("phone") String phone);
    
        //保存新创建的学生信息
        int saveStudent(Student student);
    
        //根据学生id,查询学生信息
        Student queryStudentById(@Param("id") Integer id);
    }
    
    • dao接口对应的xml文件, 位于resources/mapper目录下,这里将dao接口和dao.xml文件分开管理
    
    mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.demo.dao.StudentDao">
        
        <select id="queryStudentByPhone" parameterType="string" resultType="com.example.demo.model.Student">
            select id, name, age, phone from student where phone = #{phone}
        select>
    
        
        <insert id="saveStudent" parameterType="com.example.demo.model.Student">
            insert into student(name, age, phone) values(#{name}, #{age}, #{phone})
        insert>
    
        
        <select id="queryStudentById" parameterType="int" resultType="com.example.demo.model.Student">
            select id, name, age, phone from student where id = #{id}
        select>
    
    mapper>
    
    • 实现公共接口工程里对外提供的服务
    package com.example.demo.service.impl;
    
    import com.example.demo.dao.StudentDao;
    import com.example.demo.model.Student;
    import com.example.demo.service.StudentService;
    import org.apache.dubbo.config.annotation.DubboService;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import javax.annotation.Resource;
    
    @DubboService(interfaceClass = StudentService.class, version = "1.0.0", timeout = 5000)
    public class StudentServiceImpl implements StudentService {
    
        @Resource
        private StudentDao studentDao;
    
        @Resource
        private RedisTemplate redisTemplate;
    
        //保存新创建的学生
        @Override
        public int saveStudent(Student student) {
            int saveResult = 0;//表示保存学生信息的结果:1/添加成功 -1:手机号为空 2:手机号码重复
            if(student.getPhone() == null){
                saveResult = -1;
            }else{
                Student queryStudentResult = studentDao.queryStudentByPhone(student.getPhone());
                if(queryStudentResult != null){
                    saveResult = 2;
                }else{
                    //该学生尚未存在,保存到数据库中
                    saveResult = studentDao.saveStudent(student);
                }
            }
            return saveResult;
        }
    
        @Override
        public Student queryStudent(Integer id) {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Student.class));
            final String STUDENT_USER_KEY = "STUDENT:";
            String key = STUDENT_USER_KEY + id;
            //先尝试从缓存获取:按照key的格式来查
            Student student = (Student) redisTemplate.opsForValue().get(key);
            System.out.println("------- 从redis中查询数据 ----------> : " + student);
            if(student == null){
                //缓存中没有,需要到数据库查询:按照id格式来查询
                student = studentDao.queryStudentById(id);
                System.out.println("------- 从数据库中查询数据 ---------> : " + student);
                if(student != null){
                    //数据库中有该数据,存一份数据到redis中:按照key的格式来存
                    redisTemplate.opsForValue().set(key, student);
                }else{
                    //防止缓存穿透:对既未在缓存又未在数据库中的数据,设置默认值
                    redisTemplate.opsForValue().set(key, Student.getDefaultStudent());
                }
            }
            return student;
        }
    }
    
    • springboot主启动类上添加支持dubbo的注解并添加对dao接口扫描的注解
    package com.example;
    
    import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @EnableDubbo
    @MapperScan(basePackages = "com.example.demo.dao")
    public class StudentserviceProviderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(StudentserviceProviderApplication.class, args);
        }
    }
    

    dubbo消费者项目

    该项目为springboot项目,启动项依赖只要勾选web依赖

    pom.xml中的额外依赖均与服务提供者相同

    项目配置
    • 配置application.properties
    #springboot服务的基本配置
    server.port=9090
    server.servlet.context-path=/demo
    
    #springboot中使用dubbo的配置
    #消费者名称
    dubbo.application.name=student-service-consumer
    
    #配置需要扫描的包
    dubbo.scan.base-packages=com.example.demo.controller
    
    #配置注册中心
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
    • 同样在springboot的启动类上添加支持dubbo的注解
    package com.example;
    
    import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @EnableDubbo
    public class StudentConsumerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(StudentConsumerApplication.class, args);
        }
    }
    
    controller层
    • 消费者与前端交互的controller层, 采用RESTful接口风格
    package com.example.demo.controller;
    
    import com.example.demo.model.Student;
    import com.example.demo.service.StudentService;
    import org.apache.dubbo.config.annotation.DubboReference;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class StudentController {
    
        @DubboReference(interfaceClass = StudentService.class, version = "1.0.0")
        private StudentService studentService;
    
        @PostMapping("/student/add")
        public String addStudent(Student student){
            int saveStudentResult = studentService.saveStudent(student);
            String msg = "";
            if(saveStudentResult == 1){
                msg = "添加学生: " + student.getName() + " 成功";
            }else if(saveStudentResult == -1){
                msg = "手机号不能为空";
            }else if(saveStudentResult == 2){
                msg = "手机号: " + student.getPhone() + " 重复,请更换手机号后重试";
            }
            return msg;
        }
    
        @PostMapping("/student/query")
        public String queryStudent(Integer id){
            String msg = "";
            Student student = null;
            if(id != null && id > 0){
                student = studentService.queryStudent(id);
                if(student != null){
                    msg = "查询到的学生信息: " + student.toString();
                }else{
                    msg = "未查询到相关信息";
                }
            }else{
                msg = "输入的id范围不正确";
            }
            return msg;
        }
    }
    
    前端页面

    前端html页面和js文件位于resources/static目录下

    • 可以借助postman便捷的发送post请求来添加学生

    • 查询学生的请求可以借助如下query.html页面,通过ajax来发送查询请求

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>query.htmltitle>
        <script src="../js/jquery-1.11.1-min.js">script>
        <script type="text/javascript">
            $(function (){
                $("#stuBtn").click(function (){
                    var id = $("#stuId").val();
                    $.ajax({
                        url: "/demo/student/query",
                        data:{"id":id},
                        type:"post",
                        dataType:"text",
                        success:function (data){
                            alert(data);
                        }
                    })
                });
            });
        script>
    head>
    <body>
    <input type="text" id="stuId"><br>
    <input type="button" id="stuBtn" value="查询">
    body>
    html>
    
  • 相关阅读:
    Java基础(三)
    NebulaGraph 的云产品交付实践
    U3d力扣基础刷题-2
    【数据结构】Java实现数据结构的前置知识,时间复杂度空间复杂度,泛型类的讲解
    知识产权维权类型有哪些
    vue之浅析extend与手动挂载$mount
    安全典型配置(五)SNMP中应用ACL过滤非法网管案例
    【数据结构】栈和队列
    LeetCode SQL专项练习 排序 & 修改(2)
    数组传参及 &数组
  • 原文地址:https://www.cnblogs.com/nefu-wangxun/p/16894500.html