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目录中

编程实现

项目结构

  • 分布式总体项目结构

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

  • 公共接口项目结构

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

  • 服务提供者项目结构

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

  • 消费者项目结构

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

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.demo</groupId>             <artifactId>demo-api</artifactId>             <version>1.0.0</version>         </dependency>          <!--dubbo依赖-->         <dependency>             <groupId>org.apache.dubbo</groupId>             <artifactId>dubbo-spring-boot-starter</artifactId>             <version>2.7.8</version>         </dependency>           <!--zookeeper依赖-->         <dependency>             <groupId>org.apache.dubbo</groupId>             <artifactId>dubbo-dependencies-zookeeper</artifactId>             <version>2.7.8</version>             <type>pom</type>             <exclusions>                 <!-- 排除log4j的依赖-->                 <exclusion>                     <artifactId>slf4j-log4j12</artifactId>                     <groupId>org.slf4j</groupId>                 </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文件分开管理
<?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="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>      <!--         根据学生id,查询学生信息     -->     <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来发送查询请求

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>query.html</title>     <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> 

发表评论

相关文章