Springboot:完整的项目编写流程

总结自:

Springboot:IntelliJ运行配置Spring Boot项目 - ShineLe - 博客园

 

第一部分,项目生成

1、用IDEA新建一个SpringBoot项目

File→New→Project→Spring Initializr→项目名为springboot_example1

注意JDK与Java版本的匹配

2、添加依赖

  • Lombok

  • Spring Web
  • Mybatis Framework

第二部分,项目基本结构

3、在Application类(main方法所在类)的同级目录中,构造以下package

package名

作用

bean 数据库表对应的Entity类,每个字段都是表中的一列
mvcconfig

WebMvc配置类所在的包

swaggerconfig Swagger配置类所在的包
controller

Controller层

定义前端请求后端接口间的映射关系

这里只是映射,具体的业务逻辑方法在services包中说明

dao

数据持久化层

定义接口dao,其中定义了数据库查询方法

具体的方法实现在mapper.xml中完善

services 接口层,定义后端接口的具体业务逻辑方法的实现

构造这些包之后就会有一个类似于下边这样的项目结构,常规的SpringBoot项目基本上结构组织就是这样了。

4、在resources目录下,新建目录mapper,其中存放与mapper.xml相关的文件

 

第三部分,数据库相关

5、bean包(所有包在文档结构中的位置看第3节)

bean包一般存放两种结构:1)某个数据库表对应的Java类;2)前后端通信时,需要同时传输多种不同类型的数据,这时可以把这些数据用一个Java类封装,传输时传输这个类的对象。这两种结构在下边举例展示:

1)数据库表对应的Java类

明确数据库表结构,为每个表构造一个entity类,放在bean包中,假设数据库表名xxx,则它对应的entity类名Xxx

①现在有user

user表,四个属性列:

  • id
  • username
  • password
  • identity

②这个表对应的entity类User,放在bean包

注意属性名与数据库列名的对照:

public class User {
    private Long id;
    private String username;
    private String password;
    private String identity;
    //getter与setter方法
    //...
}

注:getter、setter方法可以通过右键 Generator自动生成

在完成数据库表的bean后,其他与数据库相关的内容包括数据持久化具体SQL方法的编写,这两个功能分别在dao包mapper目录中。

2)前后端通信时传输一些不同数据类型的数据

例如,后端收到前端某个Request之后,要向前端Response一些数据,这些数据分为三种:

  • id:Integer类型
  • data:List<Integer>
  • tips:String

如何一个Response就能同时把这三种数据都发过去?这时不能用Map,因为Map要求<K-V>中的V是同一种类型。

解决方法就是自己设置一个Java类,这个类有三个变量id,data,tips,类型就是我们上边所说的三中类型,之后在Response时通过这个类的某个对象向前端传输。

这个类就放在bean包下:

public class Res {
    private Integer id;
    private List<Integer> data;
    privavte String tips;
    //...setter getter方法...
    ...
}

这样在写接口时,我们就可以用这个Bean作为返回值类型,return一个它的对象:

@GetMapping(path = "...")
public Res func1() throws Exception{
    Res r = new Res();
    //r.setXx(...);
    //...
    return r;
}

 

6、dao包:数据持久化层

6.1、说明

dao包存放dao接口,命名规则为XxxDao

dao接口中声明了数据库操作的相关方法,业务逻辑层(Service层)的各项功能通过dao方法实现与数据库的交互

  • 注册时,需要按用户名查询,注册成功后往数据库中插入新用户的信息;
  • 登录时,要同时查询用户名与密码。

6.2、注解

  • @Repository:表示这是一个配置类。
  • @Mapper:表示需要去mapper.xml文件中去找sql代码的具体实现。

6.3、代码

由于dao中只声明了相关方法,所以代码比较简单,只需要注意几个注解:

@Repository
@Mapper
public interface UserDao {
    //查询用户是否存在,若是存在则提示已存在
    User findUserByName(String name);

    //新增用户
    void register(String name, Integer age, String pwd);

    //用户登录--根据用户名和密码查询ID是否存在
    Long login(String name, String pwd);

    User findUserById(Long id);
  
  void update(User user);
}

 

7、mapper文件夹

第6节中所写的dao包中的dao接口,只声明了方法,并没有实现,具体的实现在mapper文件夹的XxxMapper.xml中:

关于mapper文件的写法,可以看Springboot:mybatis与mapper.xml - ShineLe - 博客园

代码

<?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.yy.springbootmybatisxml.dao.UserDao" >
    <resultMap id="BaseResultMap" type="com.yy.springbootmybatisxml.bean.User" >
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
        <result column="pwd" property="pwd" jdbcType="VARCHAR" />
        <result column="age" property="age" jdbcType="INTEGER" />
    </resultMap>

    <sql id="Base_Column_List" >
        id, name, age, pwd
    </sql>

    <select id="findUserByName" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from user
        where name=#{name}
    </select>

    <select id="login" resultType="java.lang.Long">
        select u.id from user u where u.name=#{param1} and u.pwd=#{param2}
    </select>

    <select id="findUserById" resultMap="BaseResultMap">
        select * from user u where u.id=#{id}
    </select>

    <insert id="register">
        insert into user (name,age,pwd) values(#{param1},#{param2},#{param3})
    </insert>

<update id="update" parameterType="com.项目名.bean.User">   UPDATE user set name=#{name}, age=#{age}, pwd=#{pwd}   WHERE id=#{id} </update> </mapper>

 要点

1)mapper的namespace属性,必须是第6节所说的dao名,且说明完整路径com.路径.XxxDao

<mapper namespace="com.路径.dao.UserDao" >
    ...
</mapper>

2)不同的块功能也不同:

插入是<insert>

查询是<select>

删除是<delete>

更新是<update>

3)关于块的属性的含义:

  • id:这个块对应的dao中的方法名。

<!--对应dao包中的findUserById方法-->
<select id="findUserById"...>
  • parameterType:如果dao中对应的方法传入一个Bean参数,就要用属性parameterType指明这个Bean的具体路径。这样,在下边SQL语句中如果要提取这个Bean的各个成员变量的数值时,就可以写为#{变量名}的形式,此时会自动把 bean.变量的值传入SQL中:
    <update id="update" parameterType="com.项目名.bean.User">
            UPDATE user set name=#{name}, age=#{age}, pwd=#{pwd}
            WHERE id=#{id}
    </update>

    下边这段对应dao中的update方法:

    @Repository
    @Mapper
    public interface UserDao {
           ...
      void update(User user);
    }

    update方法传入一个User对象user,这样最终查询的SQL语句就变成了:

    <!--mapper文件中的写法-->
            UPDATE user set name=#{name}, age=#{age}, pwd=#{pwd}
           WHERE id=#{id}    
    
    <!--实际效果-->
            UPDATE user set name=user.name, age=user.age, pwd=user.pwd
           WHERE id=user.id

    这样就实现了通过Bean向SQL一次传多个参数的效果。

  • resultType:如果查询结果是单个值,就用resultType指明这个值的类型。

    <select id="login" resultType="java.lang.Long">
            select u.id from user u where u.name=#{param1} and u.pwd=#{param2}
        </select>

    上边这段代码查询了单个id并以Long类型返回。

  • resultMap:如果查询了多列(多个属性)的值,就需要用一个Bean来承接(映射)。它对应一个bean包下的类,借助这个bean对象返回:
    <resultMap id="BaseResultMap" type="com.路径.bean.User" >
            <id column="id" property="id" jdbcType="INTEGER" />
            <result column="name" property="name" jdbcType="VARCHAR" />
            <result column="pwd" property="pwd" jdbcType="VARCHAR" />
            <result column="age" property="age" jdbcType="INTEGER" />
        </resultMap>
    
        <select id="findUserByName" resultMap="BaseResultMap">
            select
            <include refid="Base_Column_List" />
            from user
            where name=#{name}
        </select>

    <resultMap>块中:

    各项名称 说明
    id 映射关系的名称,和下边SQL块的resultMap属性对应
    type 将查询结果映射到哪个Bean来承接,type指明这个Bean的路径
    <id>块:id列

    column:sql查询结果中的列名

    property:对应的Bean中的变量名

    jdbcType:这一列的类型,是java.sql.Type中有的类型,具体有哪些类型可以看:JDBC总结2.3.2节

    <result>块:其他列

    之后在Java代码中就可以用List<Bean>来承接所有结果了,如下代码:

  •         List<User> result = new ArrayList<User>();
            try {
                result = userDao.findUserByName(name);
            } catch (Exception e) {}

    这个List中的每个User对象就代表一行查询结果。

4)如果要查询多列,SQL语句有两种写法:直接写借助<sql>块

例如,查询user表的id,name,age,pwd四列:

<!--直接写-->
<select id="findUserByName" resultMap="BaseResultMap">
        select
        id,name,age,pwd 
        from user
        where name=#{name}
</select>

<!--借助sql块,此时通过sql块的id嵌入到sql语句中-->
<sql id="Base_Column_List" >
        id, name, age, pwd
</sql>

<select id="findUserByName" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from user
        where name=#{name}
</select>

一般情况下直接写就可以,<sql>块通常用于有许多不同的SQL语句需要对相同的列进行操作的情况下。

5)dao方法传入的参数,通过#{参数名}传进xml文件。

如果是直接传入的参数,就直接把值填充到#{参数名}处;

如果是传入一个Bean对象,就会以bean.变量的形式填充到#{变量名}处(这种需要结合parameterType属性使用,具体见本节第3)部分)。

     <!--直接传参,对应dao方法 register(String name,Integer age,String pwd)-->   
    <insert id="register">
        insert into user (name,age,pwd) values(#{name},#{age},#{pwd})
    </insert>

    <!--通过bean传参,对应dao方法 register(User user)-->
    <insert id="register" parameterType="com.路径.User">
        insert into user (name,age,pwd) values(#{name},#{age},#{pwd})
    </insert>

6)查询到的结果如何传递到Java代码中去?通过resultMap属性实现(具体见本节第3)部分)

补充

项目如何得知该去哪里去找dao和它对应的mapper.xml呢?

①通过在配置文件properties或yml中,添加如下配置项

mybatis.typeAliasesPackage: com.路径.dao
mybatis.mapperLocations: classpath:mapper/*.xml

 ②为dao接口添加注解(这一点在第6节有说)

@Repository
@Mapper
public interface UserDao {
    ...
}

第四部分,业务逻辑相关

8、services包

8.1、说明

services包中存放services接口具体的实现类

这些接口与实现类的功能是,借助dao包中提供的数据库相关功能,完成前端要求功能所需的数据的查询与插入,并返回相关的Bean类对象

比如前端有一个注册、登录功能:

  • 注册需要判断数据库中是否已经有重名的用户,则需要查询数据库,注册成功后则需要把用户信息插入到数据库中;

  • 登录需要判断用户名和密码是否匹配,同样需要查询数据库。

当然,这些查询和插入功能的具体代码,都是在dao实现的,services包的相关方法只是调用dao包的相关方法。

这些方法的返回值,一般是查询或者插入影响的entity类对象

命名规则为:

  • 接口为XxxServices,其中只是简单地声明相关业务逻辑方法,没有别的代码和注解;

  • 接口实现类为XxxServicesImpl,继承自XxxServices,需要实现这些业务逻辑方法

8.2、注解

注解

说明

@Service

写在XxxServicesImpl之前,表示这是一个Services配置类。

这样,在用@Autowired注入Services接口后,就会自动调用这个类实现的方法。

@Autowired

private UserDao userDao

注入dao,进行数据库相关操作,包括查询、持久化等。

 注意,如果有多个Service接口实现类,要如何确定注入的是哪一个?

Springboot:@Service注解作用 以及 当有多个Impl时如何知道注入的是哪个Impl - ShineLe - 博客园

8.3、代码

以注册和登录为例,二者都涉及对user表的操作,所以可以统一放在UserServices接口与UserServicesImpl实现类中:

接口UserServices

定义注册和登录方法,register与login:

public interface UserServices {
    User register(String name, Integer age, String pwd);
    User login(String name, String pwd);
}

接口实现类UserServicesImpl(需要注意的点,都标红加粗了)

@Service
public class UserServicesImpl implements UserServices {
    @Autowired
    private UserDao userDao;

    @Override
    public User register(String name, Integer age, String pwd){
        ...
    }

    @Override
    public User login(String name, String pwd){
        ...
    }
}

注册:register

public User register(String name, Integer age, String pwd) {

    User existUser = userDao.findUserByName(name);
    User result = new User();
    try {
        if (existUser != null) {
            result.setXxx(...);
        } else {
            userDao.register(name, age, pwd);//注册后再使用该名字查询用户信息
            User currentRegUser = userDao.findUserByName(name);
       result.setXxx(...);
        }
    } catch (Exception e) {
        e.printStackTrace();
        result.setMsg(e.getMessage());
    }
    return result;
}

登录:login

public User login(String name, String pwd){
    User result = new User();
    result.setSuccess(false);

    Long userId = userDao.login(name,pwd);
    try{
        if(userId != null){
            result.setMsg("登录成功!");
            result.setSuccess(true);
            //根据userId查询出当前登录用户
            User currentLoginUser = userDao.findUserById(userId);
        result.setXxx(...);
        }else{
            result.setMsg("用户名或密码不正确!");
            result.setSuccess(false);
        }
    } catch (Exception e){
        e.printStackTrace();
        result.setMsg(e.getMessage());
    }
    return result;
}

观察上述代码,发现格式一般是:

public User sercice_func(...){
    User result = new User();
    
    xxx = userDao.dao_func(...);

    try{
        if(xxx != null){
            //sql成功,成功接收返回值
            result.setXxx(...);
            result.setMsg("成功!");
        }else{
            //sql失败
            result.setXxx(...);
            result.setMsg("失败,...");
    }catch (Exception e){
            e.printStackTrace();
            result.setMsg(e.getMessage());
    }
    return result;
}

 

9、controller包

9.1、说明

逻辑控制层

负责调控前端URL后端处理方法之间的映射,即前端请求某个URL,controller层负责将该请求转达给专门的后端处理方法,该后端方法中会使用Services层中的方法进行具体的业务逻辑处理

定义后端接口,工作原理为:

  1. 前端向指定的URL发送请求;
  2. 后端接收到请求,根据URL的格式,查找controller层中的不同方法,将该请求传给符合条件的方法进行处理;
  3. 处理完毕后返回response(这个response可以是一个html页面,也可以是json对象和json字符串)

 

关于controller层,我曾总结过相关内容,可以看:

Springboot是如何实现页面间的跳转的 - ShineLe - 博客园:介绍Controller层如何调配URL与界面跳转

Springboot中的Controller - ShineLe - 博客园 : 介绍Controller层的工作原理,以及返回一个html页面的方法

Springboot:前后端分离——提交url返回json - ShineLe - 博客园:介绍如何用controller层接收json数据,返回json数据

9.2、注解

  • @Controller:写在Controller类之前,表示这是一个Controller。
  • @RestController:写在Controller类之前,添加该注解即可返回JSON格式的数据;@RestController = @Controller + @ResponseBody,@ResponseBody注解是将return的数据结构转换为JSON格式。
  • @RequestMapping("/url"):写在Controller类前或类方法前,表示该类或该方法映射的URL,当我们访问该URL时,就会自动调用相应的处理方法;
  • @PostMapping:相当于@RequestMapping(method = ReuqestMethod.POST )
  • @GetMapping:相当于@RequestMapping(method = ReuqestMethod.GET )
  • @PathVariable(GET专用):通常我们用URL Template样式映射,即url/{param}这种形式,我们可以获取到URL后的参数
  • @RequestParam(POST、GET均可):用于获取多个参数,以及application/x-www-form-urlencoded传来的JSON对象,在()传入需要获取参数的参数名
  • @RequestBody(POST专用)用的最多的注解,该注解与@RequestParam作用类似,区别在于将①传来的多个参数用Map或Entity统一接收,而不用对每个参数分别写注解;②application/json传来的Json字符串的处理;

如果要注入UserServices,可以用@Autowired,由于我们已经实现了UserServicesImpl,且添加了@Service,所以可以正常使用这个接口方法。

9.3、代码

@Slf4j
@Controller
@RequestMapping(value = "/url路径")
public class UserController {

@Autowired private UserServices userServices; @RequestMapping(value = "/register", method = RequestMethod.POST)public String register(String name, Integer age, String pwd, Model model, HttpServletRequest request, HttpServletResponse response)throws Exception{ try{ //打印日志 log.info(name+","+age+","+pwd); //获取注册的结果 User result = userServices.register(name, age, pwd); if(result.isSuccess()){ //将结果存到model里面,用于前端view层展示 model.addAttribute("result",result); //跳转至注册结果页面 return "registerResult"; }else{ response.setContentType("application/json; charset=utf-8"); response.getWriter().print("{\"code\":\"0002\",\"msg\":\"用户名已存在,注册失败!\"}"); } }catch (Exception e){ e.printStackTrace(); } return null; } @RequestMapping(value = "/login", method = RequestMethod.POST)public String login(String name, String pwd, Model model, HttpServletRequest request, HttpServletResponse response) throws Exception{ try{ //打印日志 log.info(name+","+pwd); //获取登录查询的结果 User result = userServices.login(name, pwd); if(result.isSuccess()){ //将结果存到model里面,用于前端view层展示 model.addAttribute("result",result); HttpSession session = request.getSession(); session.setAttribute("name", result.getName()); session.setAttribute("password",result.getPwd()); System.out.println("登录后的sessionID:"+session.getId()); //跳转至登录结果页面 return "loginResult"; }else{ response.setContentType("application/json; charset=utf-8"); response.getWriter().print("{\"code\":\"0001\",\"msg\":\"用户名或密码错误!\"}"); } }catch (Exception e){ e.printStackTrace(); } return null; }

 

这样,整个程序的结构就是:

 

Controller层与Services层的区别(个人理解):

 

就在于最后的返回内容,Controller层会直接向前端发送Response,用于显示,所以需要组织Response的内容;而Services层则不需要管前端和后端,只是专注于业务的实现,比如注册,就要判断是否有重名。

第五部分,配置文件

10、mvcconfig包

这个文件是简单的实现URL与HTML文件之间的映射,如果我们已经在Controller类中明确说明了映射关系,就可以不要这个包。

具体可以看:Springboot是如何实现页面间的跳转的 - ShineLe - 博客园方法二。

11、swaggerconfig包

这个包主要负责接口API Swagger文档的实现

具体可以看:Swagger与Docket - ShineLe - 博客园

 

第六部分,部署到服务器

Springboot部署到阿里云服务器 

posted @ 2022-07-15 16:14  ShineLe  阅读(5114)  评论(0编辑  收藏  举报