5

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

 1 year ago
source link: https://www.cnblogs.com/nefu-wangxun/p/16894500.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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.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接口
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>

Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK