DDD架构和微服务初步实现

本次记录的是微服务的初步认识和DDD架构的初步实现和思路,在之前的发布里,对Javaweb进行了一次小总结,还有一些东西,不去详细理解说明了,下面开始我对微服务的理解。

什么是微服务?

在刚刚开始学习的时候我是懵逼的,微服务是什么,springcloud是什么,搜了一些相关文章发现全是官方语言,还是不太懂,在后面边敲边学的过程中,对我而言,我自己对微服务也有了一个起步的认知:springcloud(微服务)是多个springboot项目的集合管理

让我举个例子:在以前我们写一个项目的时候,比如图书管理系统,整个项目模块的任务,就是围绕图书的增删改查来操作,如果我们想实现用户管理,我们需要再次新建一个controller,两者是没有任何关联的,业务完全不同,可是却互相依赖,如果我们还有电影管理系统,当图书管理系统出现问题导致tomcat宕机,那整个网站都挂了

1.maven的依赖不清晰,比较混乱不容易管理

2.部署在一个服务器上压力大

3.无法实现业务解耦

我们可以提前理解一下,在类中直接引用的耦合度低,还是通过接口访问获取到资源低。一定是通过接口访问获取的耦合度低,后面会提到。

4.代码全在一个项目中,部署速度慢

5.协同开发困难

假如和小伙伴们开发一个管理系统,我来开发用户登录业务,你来开发图书的增删改查,我们是需要不停的拉取推送,拉取推送,效率很低,而且容易出错,使用微服务就可以完全解耦,我针对我的业务逻辑,你只关注你的业务,互不干扰

目前我对spring could的理解是:springboot项目集合 + 负载均衡,Feign调用,nacos,gateway网关等 组成了springcould。所以新知识很少,springcould没有那么神秘了。

DDD架构

下面的内容与微服务的关系不大,都是基于springboot的实现

什么是DDD架构,大家可以去自行搜索一下,太官方的我就不说了,我就说一下我对DDD的理解

在DDD之前我们需要知道MVC架构模式,相比大家都很熟悉了,controller调service层,service调dao层,controller暴露接口执行对应的业务,service执行业务逻辑,dao层操作数据库。

DDD也是类似的,只不过增添了一些规则和划分,变成了四层模式:

分层架构的一个重要原则是每层只能与位于其下方的层发生耦合,较低层绝不能直接访问较高层。
严格分层架构:
某层只能与位于其直接下方的层发生耦合
松散分层架构:
则允许某层与它的任意下方层发生耦合
我们在实际运用过程中多使用的是松散分层架构。

api(接口层):提供微服务之间的相互调用的接口

application(应用层):对用户传入的参数进行校验等处理与吞吐转换

domain(领域层):业务的主要逻辑

infra(基础设施层):数据库的交互

(偷懒了,自己不画图了哈哈哈)

差不多就这些,如果大家清楚mvc架构模式,应该能理解,只不过增加了一个微服务之间的调用,大家可以不去关注,我们后面才会用到。

下面我们开始进行微服务的第一步,先把整个骨架搭起来

一:骨架搭建

本次我实现的还是一个简单的增删改查,这次就先写查询了,都是差不多的,相信大家都是会写的,主要是说一下思路和一些工具

首先我们需要建立一个空项目,切记兄弟们,空项目,不要maven项目

紧接着我们需要建立一个maven模块,这个模块就是一个项目(我已经建好了)

我们可以这样理解,movie是一个网站,movie-catalogue是这个网站的一个功能,用来实现电影的增删改查,后面还会在movie下再建立模块,由一个一个的模块来组成一个项目。

下面我们继续搭建骨架,我们需要准备刚刚说的四个层模块,如下图:

application,domain,infra,api,作用如上文一样,而application-controller存放的是对应的controller,因为以后可能会添加mq和job定时任务,以后再说

这里我多增加了两个模块,一个是common层,一个是starter

common:项目的公共模块,比如说定义的枚举,用户的上下文,和一些工具类,都放在这里

starter:项目的启动模块,springboot总需要启动吧,没有任何的其他功能,只负责启动和配置文件的编写

二:建包

下面开始建包

application-controller中我们需要建立三个包

controller:处理用户请求和返回,对参数校验

convert:负责层与层的数据转换的

dto:封装前端请求与返回的数据

convert没有接触过,它是负责数据的转换,比如我们在执行完业务逻辑后返回数据,前端可能需要再一次的封装,举个例子:传入id查询电影信息,我们查询完电影信息后可以再封装一次,把当前请求的用户也返回出去,而不影响数据库的交互

common层:

entity:一些通用的实体,比如说封装的返回值和分页

enums:通用的枚举

domain层:

bo:业务执行的实体类

convent:与controller层一致,数据的转换,与基础层交互

service:业务接口与实现

infra层:

entity:数据库的映射实体类

mapper:数据库访问层

service:服务接口与实现

mapper:sql实现

starter层:

不多说了,spring boot的启动和配置文件

三:引入依赖

最恶心的就是这里了,也是开始最头疼的点,我们需要好多好多依赖,没什么技巧,大家只能慢慢顺下去,看到一个依赖就去查一下,知道自己想干什么,下面我来带大家引入一遍

 

不要搞错了,是父模块下的pom文件,这里规定了springboot和springcloud的版本

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.4.2</version><type>pom</type><scope>compile</scope></dependency></dependencies><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.4.2</spring-boot.version></properties>

starter: 

    <dependencies><dependency><groupId>com.yizhiliulianta</groupId><artifactId>movie-catalogue-application-controller</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies><build><finalName>${project.artifactId}</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.3.0.RELEASE</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build>

引入controller层,因为DDD架构中上层引用下层,所以最上层一定是包含最下层的,所以之间引用最上层即可,大部分依赖我都写上注释了,大家有不知道的自行搜索吧,我就不去细说了

 infra:

    <dependencies><!-- jdbcStarter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><version>2.4.2</version></dependency><!-- druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.21</version></dependency><!-- mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.22</version></dependency><!-- mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency><!-- 集合工具类--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.4</version></dependency><dependency><groupId>com.yizhiliulianta</groupId><artifactId>movie-catalogue-common</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

引入common是因为common中包含着通用的依赖,因为上层要依赖基础层,所以我们直接在基础层引入通用的依赖即可,当上层引入infra的时候,也就引入了common通用的依赖

domain:

    <dependencies><!-- JSON 的实现--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.12.7</version></dependency><!-- 实现数据绑定和对象序列化--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.7</version></dependency><dependency><groupId>com.yizhiliulianta</groupId><artifactId>movie-catalogue-infra</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

领域层引入基础设施层,不多说了

common:

    <dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version></dependency><!-- 数据映射--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version></dependency><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version></dependency><!-- 日志--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId><version>2.4.2</version></dependency><!--  json解析工具--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.24</version></dependency><!-- 字符串工具--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version></dependency><!-- 断言检查--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>19.0</version></dependency></dependencies>

通用依赖

controller:

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.4.2</version><exclusions><exclusion><artifactId>spring-boot-starter-logging</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><dependency><groupId>com.yizhiliulianta</groupId><artifactId>movie-catalogue-domain</artifactId><version>1.0-SNAPSHOT</version></dependency></dependencies>

禁用logging,防止日志冲突,一样的引用下层的domain

到这里依赖就引用完成了,让我们启动一下

MovieApplication类:

package com.yizhiliulianta.movie;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;@SpringBootApplication
@ComponentScan("com.yizhiliulianta")
@MapperScan("com.yizhiliulianta.**.mapper")
public class MovieApplication {public static void main(String[] args) {SpringApplication.run(MovieApplication.class);}
}

 application.yml:

server:port: 1000spring:datasource:username: rootdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/movieweb?useUnicode=true&serverTimezone=Asia/Shanghai&useSSL=true&characterEncoding=utf-8password: 123456mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

我先把数据库连上了,大家按照自己的更改吧,下面是mybatis的日志更改

成功了!哈哈哈哈

下面我们开始代码的编写

代码实现 

我们先从common层开始写,我们首先要知道我们要返回什么,我比较习惯从上往下写。

我想返回给前端的应该是这样的一个格式,清楚之后,上代码

common模块:

找到common层的枚举包(enums)

package com.yizhiliulianta.movie.common.enums;import lombok.Getter;@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(500,"失败");public int code;public String desc;ResultCodeEnum(int code, String desc) {this.code = code;this.desc = desc;}public static ResultCodeEnum getByCode(int codeVal){for (ResultCodeEnum resultCodeEnum : ResultCodeEnum.values()){if (resultCodeEnum.code == codeVal){return resultCodeEnum;}}return null;}
}

定义返回的状态码,成功200,失败500,getByCode方法是通过code获取对应的枚举。

common下的entity(通用实体类包)

package com.yizhiliulianta.movie.common.entity;import com.yizhiliulianta.movie.common.enums.ResultCodeEnum;
import lombok.Data;@Data
public class Result<T> {private Boolean success;private Integer code;private String message;private T data;public static <T> Result ok(T data){Result result = new Result();result.setSuccess(true);result.setCode(ResultCodeEnum.SUCCESS.getCode());result.setMessage(ResultCodeEnum.SUCCESS.getDesc());result.setData(data);return result;}//有数据返回失败public static <T> Result fail(T data){Result result = new Result();result.setSuccess(false);result.setCode(ResultCodeEnum.FAIL.getCode());result.setMessage(ResultCodeEnum.FAIL.getDesc());result.setData(data);return result;}}

大家可以对着看一下,就是一个对应的实体类,两个方法,一个成功的返回实体类,一个失败的。

controller模块:

dto包:

package com.yizhiliulianta.movie.application.dto;import lombok.Data;import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;@Data
public class MovieDTO implements Serializable {/*** 序号*/private Long id;/*** 电影名字*/private String moviename;/*** 电影评分*/private Double moviescore;/*** 自定义 getter 方法,用于处理 moviescore 的位数问题*/public Double getMoviescore() {// 在这里处理位数问题,例如将 moviescore 保留一位小数if (this.moviescore != null) {return BigDecimal.valueOf(this.moviescore).setScale(1, RoundingMode.HALF_UP).doubleValue();} else {return null;}}
}

这里封装前端传来和返回的实体类,对参数进行了处理,评分只保留一位小数

convert包:

package com.yizhiliulianta.movie.application.convert;import com.yizhiliulianta.movie.application.dto.MovieDTO;
import com.yizhiliulianta.movie.domain.bo.MovieBO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;@Mapper
public interface MovieDTOConverter {MovieDTOConverter INSTANCE = Mappers.getMapper(MovieDTOConverter.class);MovieDTO convertBoToMovieDTO(MovieBO movieBO);MovieBO convertDtoToMovieBO(MovieDTO movieDTO);}

两个类型的数据转换实现,进入domain层之前要将dto转成bo,domain层处理完后吐出bo,我们需要再进行处理,将bo转成dto返回前端

controller包:

package com.yizhiliulianta.movie.application.controller;import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import com.yizhiliulianta.movie.application.convert.MovieDTOConverter;
import com.yizhiliulianta.movie.application.dto.MovieDTO;
import com.yizhiliulianta.movie.common.entity.Result;
import com.yizhiliulianta.movie.domain.bo.MovieBO;
import com.yizhiliulianta.movie.domain.service.MovieDomainServcie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/movie")
@Slf4j
public class MovieController {@Resourceprivate MovieDomainServcie movieDomainServcie;@PostMapping("/getMovie")public Result<MovieDTO> getMovie(@RequestBody MovieDTO movieDTO) {try {if (log.isInfoEnabled()) {log.info("MovieController.getMovie.dto:{}", JSON.toJSONString(movieDTO));}Preconditions.checkNotNull(movieDTO.getId(), "电影id不能为空");// 吞入前的数据映射MovieBO movieBO = MovieDTOConverter.INSTANCE.convertDtoToMovieBO(movieDTO);// 吞入MovieBO bo = movieDomainServcie.selectMovie(movieBO);//吐出前的数据映射MovieDTO movie = MovieDTOConverter.INSTANCE.convertBoToMovieDTO(bo);//吐出return Result.ok(movie);} catch (Exception e) {log.error("MovieController.getMovie.error{}", e.getMessage(), e);return Result.fail("查询失败");}}}

在应用层首先进行参数校验和日志的打印,像我刚刚说的,执行数据的吞吐,和数据的映射,该层不涉及任何业务逻辑,只做参数处理

domain模块:

bo包:

package com.yizhiliulianta.movie.domain.bo;import lombok.Data;import java.io.Serializable;@Data
public class MovieBO implements Serializable {/*** 序号*/private Long id;/*** 电影名字*/private String moviename;/*** 电影评分*/private Double moviescore;
}

和dto没什么区别,大家可以往里增添什么的,都是可以的,很灵活

convert包:

package com.yizhiliulianta.movie.domain.convert;import com.yizhiliulianta.movie.domain.bo.MovieBO;
import com.yizhiliulianta.movie.infra.entity.TMovie;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;@Mapper
public interface MovieBOConverter {MovieBOConverter INSTANCE = Mappers.getMapper(MovieBOConverter.class);TMovie convertBoToMovie(MovieBO movieBO);MovieBO convertToMovieBO(TMovie movie);}

与controller层的数据转换没什么区别,这是变成了与基础层的数据转换,将bo转成数据映射的实体类进行查询,查完之后再变成bo,吐给controller

service包:

package com.yizhiliulianta.movie.domain.service;import com.yizhiliulianta.movie.domain.bo.MovieBO;public interface MovieDomainServcie {MovieBO selectMovie(MovieBO movieBO);}

service.impl包:

package com.yizhiliulianta.movie.domain.service.impl;import com.alibaba.fastjson.JSON;
import com.yizhiliulianta.movie.domain.bo.MovieBO;
import com.yizhiliulianta.movie.domain.convert.MovieBOConverter;
import com.yizhiliulianta.movie.domain.service.MovieDomainServcie;
import com.yizhiliulianta.movie.infra.entity.TMovie;
import com.yizhiliulianta.movie.infra.service.TMovieService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service
@Slf4j
public class MovieDomainServiceImpl implements MovieDomainServcie{@Resourceprivate TMovieService movieService;@Overridepublic MovieBO selectMovie(MovieBO movieBO) {TMovie tmovie = MovieBOConverter.INSTANCE.convertBoToMovie(movieBO);TMovie movie = movieService.queryById(tmovie.getId());MovieBO bo = MovieBOConverter.INSTANCE.convertToMovieBO(movie);if (log.isInfoEnabled()) {log.info("MovieController.selectMovie.bo:{}",JSON.toJSONString(bo));}return bo;}
}

这里与刚刚说的一样,就是数据的转换,去调用基础层进行数据库的查询

下面基础设施层的东西,我就不去讲了,没什么东西,我是用一个小插件做的,带大家使用一下

我先把代码贴出来:

infra模块:

entity包:

package com.yizhiliulianta.movie.infra.entity;import java.io.Serializable;/*** (TMovie)实体类** @author makejava* @since 2024-06-11 10:50:08*/
public class TMovie implements Serializable {private static final long serialVersionUID = -68583078203147531L;/*** 序号*/private Long id;/*** 电影名字*/private String moviename;/*** 电影评分*/private Double moviescore;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getMoviename() {return moviename;}public void setMoviename(String moviename) {this.moviename = moviename;}public Double getMoviescore() {return moviescore;}public void setMoviescore(Double moviescore) {this.moviescore = moviescore;}}

mapper包:

package com.yizhiliulianta.movie.infra.mapper;import com.yizhiliulianta.movie.infra.entity.TMovie;
import org.apache.ibatis.annotations.Param;
import java.util.List;/*** (TMovie)表数据库访问层** @author makejava* @since 2024-06-11 10:50:07*/public interface TMovieDao {/*** 通过ID查询单条数据** @param id 主键* @return 实例对象*/TMovie queryById(Long id);/*** 统计总行数** @param tMovie 查询条件* @return 总行数*/long count(TMovie tMovie);/*** 新增数据** @param tMovie 实例对象* @return 影响行数*/int insert(TMovie tMovie);/*** 批量新增数据(MyBatis原生foreach方法)** @param entities List<TMovie> 实例对象列表* @return 影响行数*/int insertBatch(@Param("entities") List<TMovie> entities);/*** 批量新增或按主键更新数据(MyBatis原生foreach方法)** @param entities List<TMovie> 实例对象列表* @return 影响行数* @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参*/int insertOrUpdateBatch(@Param("entities") List<TMovie> entities);/*** 修改数据** @param tMovie 实例对象* @return 影响行数*/int update(TMovie tMovie);/*** 通过主键删除数据** @param id 主键* @return 影响行数*/int deleteById(Long id);}

service包:

package com.yizhiliulianta.movie.infra.service;import com.yizhiliulianta.movie.infra.entity.TMovie;/*** (TMovie)表服务接口** @author makejava* @since 2024-06-11 10:50:08*/
public interface TMovieService {/*** 通过ID查询单条数据** @param id 主键* @return 实例对象*/TMovie queryById(Long id);/*** 新增数据** @param tMovie 实例对象* @return 实例对象*/TMovie insert(TMovie tMovie);/*** 修改数据** @param tMovie 实例对象* @return 实例对象*/TMovie update(TMovie tMovie);/*** 通过主键删除数据** @param id 主键* @return 是否成功*/boolean deleteById(Long id);}

service.impl包:

package com.yizhiliulianta.movie.infra.service.impl;import com.yizhiliulianta.movie.infra.entity.TMovie;
import com.yizhiliulianta.movie.infra.mapper.TMovieDao;
import com.yizhiliulianta.movie.infra.service.TMovieService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** (TMovie)表服务实现类** @author makejava* @since 2024-06-11 10:50:08*/
@Service("tMovieService")
public class TMovieServiceImpl implements TMovieService {@Resourceprivate TMovieDao tMovieDao;/*** 通过ID查询单条数据** @param id 主键* @return 实例对象*/@Overridepublic TMovie queryById(Long id) {return this.tMovieDao.queryById(id);}/*** 新增数据** @param tMovie 实例对象* @return 实例对象*/@Overridepublic TMovie insert(TMovie tMovie) {this.tMovieDao.insert(tMovie);return tMovie;}/*** 修改数据** @param tMovie 实例对象* @return 实例对象*/@Overridepublic TMovie update(TMovie tMovie) {this.tMovieDao.update(tMovie);return this.queryById(tMovie.getId());}/*** 通过主键删除数据** @param id 主键* @return 是否成功*/@Overridepublic boolean deleteById(Long id) {return this.tMovieDao.deleteById(id) > 0;}
}

resource.mapper文件:

<?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.yizhiliulianta.movie.infra.mapper.TMovieDao"><resultMap type="com.yizhiliulianta.movie.infra.entity.TMovie" id="TMovieMap"><result property="id" column="id" jdbcType="INTEGER"/><result property="moviename" column="movieName" jdbcType="VARCHAR"/><result property="moviescore" column="movieScore" jdbcType="NUMERIC"/></resultMap><!--查询单个--><select id="queryById" resultMap="TMovieMap">selectid, movieName, movieScorefrom t_moviewhere id = #{id}</select><!--查询指定行数据--><select id="queryAllByLimit" resultMap="TMovieMap">selectid, movieName, movieScorefrom t_movie<where><if test="id != null">and id = #{id}</if><if test="moviename != null and moviename != ''">and movieName = #{moviename}</if><if test="moviescore != null">and movieScore = #{moviescore}</if></where>limit #{pageable.offset}, #{pageable.pageSize}</select><!--统计总行数--><select id="count" resultType="java.lang.Long">select count(1)from t_movie<where><if test="id != null">and id = #{id}</if><if test="moviename != null and moviename != ''">and movieName = #{moviename}</if><if test="moviescore != null">and movieScore = #{moviescore}</if></where></select><!--新增所有列--><insert id="insert" keyProperty="id" useGeneratedKeys="true">insert into t_movie(movieName, movieScore)values (#{moviename}, #{moviescore})</insert><insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">insert into t_movie(movieName, movieScore)values<foreach collection="entities" item="entity" separator=",">(#{entity.moviename}, #{entity.moviescore})</foreach></insert><insert id="insertOrUpdateBatch" keyProperty="id" useGeneratedKeys="true">insert into t_movie(movieName, movieScore)values<foreach collection="entities" item="entity" separator=",">(#{entity.moviename}, #{entity.moviescore})</foreach>on duplicate key updatemovieName = values(movieName),movieScore = values(movieScore)</insert><!--通过主键修改数据--><update id="update">update t_movie<set><if test="moviename != null and moviename != ''">movieName = #{moviename},</if><if test="moviescore != null">movieScore = #{moviescore},</if></set>where id = #{id}</update><!--通过主键删除--><delete id="deleteById">delete from t_movie where id = #{id}</delete></mapper>

EasyCode插件

下面是使用EasyCode这个插件,大家可以去idea下载一个,很方便

 右键一个表,选择EasyCode  ---》  Generate Code

module选择infra模块

package选择infra包,别选错了

然后和我一样,勾选template就可以生成了

生成后是dao包,我个人习惯喜欢mapper,将生成的类转到mapper下了,可能会有报错,大家将生成的分页代码删掉就行了

最后我们来通过aippost来测试一下

测试 

完美,成功了

总结

我们梳理一下本次实现的过程,首先建立微服务的骨架,然后引入相关依赖

代码部分:用户传入参数,将参数封装成dto传入controller,controller进行参数检查和数据转换bo并传入domain层,domain层与基础设施层交互,查询数据,再转换成bo吐出给controller,最后controller接收到再进行数据的转换,变成dto返回前端。

后面我们进行用户鉴权和微服务之间调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/29597.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Study--Oracle-03-数据库常规操作

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。 一、oracle 版本及主要功能 二、数据安装完成后常用操作SQL 1、检查数据库监听状态 监听的常用命令 启动&#xff1a;[oracleoracle u01]$ lsnrctl stop 停止&am…

2024信息系统、信号处理与通信技术国际会议(ICISPCT2024)

2024信息系统、信号处理与通信技术国际会议&#xff08;ICISPCT2024) 会议简介 2024国际信息系统、信号处理与通信技术大会&#xff08;ICISPCT2024&#xff09;将在青岛隆重开幕。本次会议旨在汇聚全球信息系统、信号处理和通信技术领域的专家学者&#xff0c;共同探索行业…

【记录46】【案例】echarts 柱状图

echarts环境4.1.0 <template><div id"threefour"></div> </template> <script> import * as echarts from "echarts" export default {name:"",components:{},data(){return {}},methods:{getdata(){var myChart…

《平衡小车控制系统》电子设计大赛校赛感悟

我们学校举行了一次电子设计大赛选拔赛&#xff0c;虽然我们在测试的时候全部都可以完成&#xff0c;最后考核的时候因为方案选择问题以及各种设计逻辑等原因没能成功晋级&#xff0c;但我能从这次备赛中学到很多东西&#xff0c;遂分享一下&#xff0c;与广大网友交流经验。&a…

英伟达发布开源模型Nemotron-4 340B

&#x1f680; 英伟达发布开源模型Nemotron-4 340B 摘要&#xff1a;英伟达最新发布的开源模型Nemotron-4 340B&#xff0c;可能彻底改变大语言模型&#xff08;LLM&#xff09;训练方式。该模型支持多种自然语言和编程语言&#xff0c;使用9万亿个token训练&#xff0c;高达9…

Day 26:2288. 价格减免

Leetcode 2288. 价格减免 句子 是由若干个单词组成的字符串&#xff0c;单词之间用单个空格分隔&#xff0c;其中每个单词可以包含数字、小写字母、和美元符号 ‘$’ 。如果单词的形式为美元符号后跟着一个非负实数&#xff0c;那么这个单词就表示一个 价格 。 例如 “$100”、…

Windows系统部署本地SQL_Server指引

Windows系统部署本地SQL_Server指引 此指引文档环境为Windows10系统&#xff0c;部署SQL_Server 2019为例&#xff0c;同系列系统软件安装步骤类似。 一、部署前准备&#xff1b; 下载好相关镜像文件&#xff1b;设备系统启动后&#xff0c;将不必要的软件停用&#xff0c;避…

【Linux】shell——条件判断test,各种运算符,expr

条件判断——test 真——0 假——1 test expression or [ expression ] 整数运算符 字符串运算符 -z 长度是否为0 -n 长度是否不为0 str1 str2 str1 ! str2 补 &&-->逻辑与&#xff0c;前面为真后面才会执行 || -->逻辑或&#xff0c;前面为假后面才…

VirtFuzz:一款基于VirtIO的Linux内核模糊测试工具

关于VirtFuzz VirtFuzz是一款功能强大的Linux内核模糊测试工具&#xff0c;该工具使用LibAFL构建&#xff0c;可以利用VirtIO向目标设备的内核子系统提供输入测试用例&#xff0c;广大研究人员可以使用该工具测试Linux内核的安全性。 工具要求 1、Rust&#xff1b; 2、修补的Q…

线代的学习(矩阵)

1.矩阵的乘法 矩阵实现满足&#xff1a;内标相等 矩阵相乘之后的结果&#xff1a;前行后列 需要注意&#xff1a;1.矩阵的乘法不具有交换律&#xff1a;AB!BA 2.矩阵的乘法满足分配律&#xff1a;A(BC) AB AC 抽象逆矩阵求逆矩阵 方法1.凑定义法、 方法2.长除法 数字型矩阵…

算法金 | 一个强大的算法模型:t-SNE !!

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 t-SNE&#xff08;t-Distributed Stochastic Neighbor Embedding&#xff09;是一种用于降维和数据可视化的非线性算法。它被广泛应用于…

LeetCode 算法:合并两个有序链表 c++

原题链接&#x1f517;&#xff1a;合并两个有序链表 难度&#xff1a;简单⭐️ 题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;…

AI智能盒子助力中钢天源设备工厂升级安全防护

中钢集团安徽天源科技股份有限公司成立于2002年3月27日,是中央企业中国中钢股份有限公司控股的上市公司&#xff0c;主导产品为永磁铁氧体器件、钕铁硼器件、四氧化三锰、锶铁氧体预烧料及各类磁选机等。 在中钢天源智能化升级过程中&#xff0c;采用并定制开发一系列厂区安全…

QT day02

思维导图 UI界面设计 设置登录界面&#xff0c;输入账号、密码&#xff0c;登录/取消 按钮 使用手动连接&#xff0c;将登录框中的取消按钮使用第二中连接方式&#xff0c;右击转到槽&#xff0c;在该槽函数中&#xff0c;调用关闭函数 将登录按钮使用qt4版本的连接到自定义…

Python期末复习题库(上)

1. (单选题) Python源程序的扩展名为&#xff08; A &#xff09; A. py B. c C. class D. ph 2. (单选题) 下列&#xff08; A &#xff09;符合可用于注释Python代码。 A. # B. */ C. // D. $ 3. (单选题)下列关于Python 语言的特点的说法中&#xff0c;错误的是&#xf…

【Linux基础IO】常见的对文件操作的函数、文件描述符fd、访问文件的本质分析

目录 fopen函数 chdir函数 fclose函数 fwrite和fread函数 open函数 umask函数 write函数 read函数 close函数 文件描述符fd 进程访问文件的本质分析 fopen函数 参数mode&#xff1a; w方式打开文件&#xff1a;1、如果被打开文件不存在&#xff0c;系统会在使用fopen函…

数据结构习题

第一章 绪论 与数据元素本身的形式、内容、相对位置、个数无关的是数据的 逻辑结构。 第二章 线性表 在一个有127个元素的顺序表中插入一个新元素并保持原来顺序不变&#xff0c;平均要移动的元素个数为 63.5。 n/2 单链表的存储密度 小于1。 创建一个包括n个结点的有序单链…

零基础入门学用Arduino 第四部分(一)

重要的内容写在前面&#xff1a; 该系列是以up主太极创客的零基础入门学用Arduino教程为基础制作的学习笔记。个人把这个教程学完之后&#xff0c;整体感觉是很好的&#xff0c;如果有条件的可以先学习一些相关课程&#xff0c;学起来会更加轻松&#xff0c;相关课程有数字电路…

Playwright-html-report源码解析

执行命令生成html格式报告 Playwright在执行完成测试&#xff0c;支持生成html格式的测试报告&#xff0c;如下图所示&#xff0c;使用"npx playwright test"执行测试&#xff0c;执行完成后&#xff0c;会提示“npx playwright show-report”命令。执行该命令&#…

创维超充车辆交付仪式暨参观座谈会圆满举行

6月14日&#xff0c;创维超充车辆交付仪式暨参观座谈会在南京成功举行。苏舜集团副总经理程璟一行以及近多出行东部大区总经理张显春一行齐聚一堂。创维汽车总裁、联合创始人吴龙八等领导亲临现场&#xff0c;对各位尊贵嘉宾的到来表示热烈欢迎&#xff0c;并与众人共同见证了这…