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层中的方法进行具体的业务逻辑处理。
定义后端接口,工作原理为:
- 前端向指定的URL发送请求;
- 后端接收到请求,根据URL的格式,查找controller层中的不同方法,将该请求传给符合条件的方法进行处理;
- 处理完毕后返回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 - 博客园