Live项目:
1. Idea IDE搭建SpringBoot
2. Mybatis-generator逆向生成Pojo、Mapper接口和XML等
3. WebMvcConfigurerAdapter资源拦截
4. Shiro的简单使用
在上述文章中,已经完成了Springboot环境的搭建、数据库的配置、Pojo、MyBatis映射文件、Dao层、MVC的配置、资源的过滤与拦截、用户的认证与权限等。Spring一般搭配SpringMvc,也就是MVC模式一起使用,那么在MVC中,我们已经完成了model的编写,接下来要写Controller层、Service层如何响应请求、如何请求数据。
Spring注解
在SpringBoot中,有如下常用的注解:
- @Controller:用于定义控制器类,在Spring羡慕中由控制器负责将用户发来的URL请求转发到对应的服务接口(Service层)
- @RequestMapping:提供路由信息,复杂URL到Controller中具体函数的映射。通常包含请求的相对地址、GET\Post等请求方式
- @ResponseBody:该注解表示该方法返回的结果直接写入HTTP Response Body中,,如果使用该注解,返回值就是Json数据,不使用该注解函数的返回值就解析为跳转路径。通常的用法就是通过Map<String,Object>来设置Json数据的键值对。
- @RestController:用户表明控制层组件,是@ResponseBody和@Controller的集合。该注解定义的类中所有的类方法返回值类型都是Json数据。
- @GetMapping@PostMapping:是一个组合注解,是@RequestMapping(method = RequestMethod.GET\POST)的缩写
- @SpringBootApplication:让SpringBoot自动给程序进行配置的注解。
- @Configuration:相当于传统的Xml配置文件,只不过该注解作用与类中,通过Java代码来设置配置。
- @Import:用来导入其他配置类
- @ImportResource:用来加载xml配置文件
- @Autowired:自动导入依赖的bean,自动注入,直接使用配置好的bean,常常用于对类成员变量、方法以及构造方法进行标注,完成自动装配工作。
- @Service:用于修饰Service层的组件
Spring层的概念
也许,看到这里,你会有些奇怪,什么是Dao层,什么是Service层,Controller层又是什么呢?为什么要用Controller来处理响应呢?@Controller注解与Controller又有什么关系呢?这里就要提到SpringMVC层的概念:
- Controller层:负责具体业务模块流程的控制,调用Service层的接口来控制业务流程,并实现url映射 。在Spring中,Controller层通过@Controller、@RestController来标注
- Service层:建立在Dao层之上,Controller之下,负责业务模块的逻辑设计,先设计接口,再完成其实现类。Service层通过@Service来标注
- Dao层:负责数据的持久化,与数据库打交道的都封装在其中,Dao的数据源在Spring配置文件中进行配置。主要还是实现数据库CRUD方法并支持Service的拓展。Dao层类通过@Dao来标注。
- Pojo:数据实体类
层的引用、请求的流程
MyBatis-generator工具已经帮我们实现了Dao层的大部分工作,但是我们还需要在Spring配置文件中配置数据源。
Dao层概览如下:
首先是Dao层的映射接口:
这里以UserMapper映射为例,如无特殊的需求(将两个实体的数据合并为一个传递给Service),一般而言,Dao层的映射文件都不需要进行修改。接口名字也清晰明了,基本上都是通过主键或者Example来对数据进行条件的筛选,然后筛选后的数据。 这里特别强调 updateByExample、updateByExampleSelective这两个接口:- updateByExample:在更新时,需要将实体对象中所有的属性都提供,不提供的属性将会赋值为NULL
- updateByExampleSelective:这个接口就明智多了,只对提供的属性进行修改,其他熟悉不变。
public interface UserMapper {
int countByExample(UserExample example);
int deleteByExample(UserExample example);
int deleteByPrimaryKey(Integer id);
int insert(User record);
int insertSelective(User record);
List<User> selectByExample(UserExample example);
User selectByPrimaryKey(Integer id);
int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example);
int updateByExample(@Param("record") User record, @Param("example") UserExample example);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
}
复制代码
Dao层具体的映射文件如下:
<?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.ljh.dao.UserMapper" >
<resultMap id="BaseResultMap" type="com.ljh.po.User" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="account" property="account" jdbcType="VARCHAR" />
<result column="password" property="password" jdbcType="VARCHAR" />
<result column="role_id" property="roleId" jdbcType="INTEGER" />
</resultMap>
<sql id="Example_Where_Clause" >
<where >
<foreach collection="oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause" >
<where >
<foreach collection="example.oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List" >
id, name, account, password, role_id
</sql>
<select id="selectByExample" resultMap="BaseResultMap" parameterType="com.ljh.po.UserExample" >
select
<if test="distinct" >
distinct
</if>
<include refid="Base_Column_List" />
from user
<if test="_parameter != null" >
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null" >
order by ${orderByClause}
</if>
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="com.ljh.po.User" >
insert into user (id, name, account,
password, role_id)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{account,jdbcType=VARCHAR},
#{password,jdbcType=VARCHAR}, #{roleId,jdbcType=INTEGER})
</insert>
<update id="updateByExample" parameterType="map" >
update user
set id = #{record.id,jdbcType=INTEGER},
name = #{record.name,jdbcType=VARCHAR},
account = #{record.account,jdbcType=VARCHAR},
password = #{record.password,jdbcType=VARCHAR},
role_id = #{record.roleId,jdbcType=INTEGER}
<if test="_parameter != null" >
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
</mapper>
复制代码
代码有所删减,在该映射文件中首先定义了xml文件的类型,随后定义了User这个类及类属性在数据库中的定义,再定义example,最后就是各个接口文件对于数据库的具体实现。
上述的类、映射文件都是通过数据库逆向生成的,那么如果我需要在SpringBoot中查询某一数据,我该到哪里去查呢?这时候就要定义数据源。在项目的application.properties文件中添加如下配置:
#数据源配置,默认使用tomcate-jdbc连接池
spring.datasource.url=jdbc:mysql://数据库地址:3306/数据库名字?useUnicode=true&characterEncoding=UTF8
spring.datasource.username=用户名
spring.datasource.password=用户密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
复制代码
Service层
既然已经打通了Dao层与数据库之间的障碍,那么我们来看一下Service层是如何实现他的价值。我觉得写代码时,只要敢想,就没有做不到的事情,一个接口可以完成多种应用场景。灵活,就是我对Service接口的评价。
public interface UserService {
...
/**
* 注册成功则无异常抛出
* 用户名/手机被使用,则抛出异常
* @param user
* @throws ServiceException
*/
void register(User user) throws ServiceException;
/**
* 查找相似的user
* @param likeUser
* @return
*/
List<User> getUsers(User likeUser);
}
复制代码
就这么一个通过传入的user对象来获取类似User的接口,实现方式和应用的场景却是变化万千。 先来看看简单的方式:
@Service
public class UserServiceImpl implements UserService {
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper; //Dao层接口
@Override
public List<User> getUsers(User likeUser) {
//构造example
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
if (null != likeUser) {
if (null != likeUser.getId()) {
criteria.andIdEqualTo(likeUser.getId());
}
if (null != likeUser.getName()) {
criteria.andNameLike("%" + likeUser.getName() + "%");
}
if (null != likeUser.getAccount()) {
criteria.andNameLike("%" + likeUser.getAccount() + "%");
}
}
return userMapper.selectByExample(example);
}
}
复制代码
在上面的这个实现方式中,通过对传入的user对象属性进行判断,如果存在,则将该条件添加到example中,随后通过Dao层获取到类似的User数据。这个接口可以用于模糊搜索用户、查看某用户信息、如果用户的属性再复杂一些,就可以实现用户排序等功能。
Controller层
先来看下Controller层处理Url请求的方式
- 通过@RequestMapping(path = {"xxx"}, method = {RequestMethod.GET/POST}) 注解,标注目前方法对应的url相对请求是xxx,并可以定义为GET、POST、DELETE等方式。
- 处理方法参数: ①@PathVariable: 获取路径参数,即url/{id}的形式 ②@RequestParam:获取查询参数,即url?name=的形式 ③@RequestBody:如果传入的是一个Body参数(通常是Json数据),那么就需要该注解对该对象进行标注,然后会对该对象进行解析与赋值。 ④Form表单的请求:如果方法参数没有标注别名,那么form表单中的名字就必须与方法参数的名字相同,否则获取不到对应的数值。
看看代码实例:
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(path = {"register"}, method = {RequestMethod.POST})
@ResponseBody
public Map<String, Object> register(String account, String password, String name, String code) {
Map<String, Object> map = new HashMap<>();
//判断传入的数据是否为空
...
//判断传入的数据格式是否正确
...
//会话状态的验证
Session session = SecurityUtils.getSubject().getSession();
try {
User user = new User();
user.setAccount(account);
if (name == null) {
user.setName(user.getAccount());
}
user.setName(name);
user.setPassword(password);
user.setRoleId(2);//设置用户角色
//调用UserService对用户进行注册操作
userService.register(user);
map.put("msg", "注册成功");
return map;
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
复制代码
上述的方法是一个Post的form表单请求,看起来参数很多,有点复杂,那么直接传入一个对象要怎么处理呢?
@RequestMapping(path = {"register"}, method = {RequestMethod.POST})
@ResponseBody
public Map<String, Object> register(@RequestBody User user) {
Map<String, Object> map = new HashMap<>();
//判断传入的数据是否为空
...
//判断传入的数据格式是否正确
...
//会话状态的验证
...
}
复制代码
使用注解@ResponseBody返回Json格式的数据,如果不使用该注解,则跳转到对应的页面。
整个请求的时序图如下: