bookmanager 用户界面分析 UserController
1 2 3 4 5 6 7 @RestController @RequestMapping(value = "/user") public class UserController { @Autowired UserService userService; ... }
@RestController 注解用于标注控制器类,表明该控制器里面的方法 默认都以 json 格式返回返回请求
在方法上方添加 @RequestMapping 注解用于配置 URL 映射,将 HTTP 请求映射到方法
@Autowired 是一种注解,可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,@Autowired 标注可以放在成员变量上,也可以放在成员变量 的 set 方法上,也可以放在任意方法 上表示,自动执行当前方法,如果方法有参数,会在 IOC 容器中自动寻找同类型参数为其传值。
前者,Spring 会直接将 UserDao 类型的唯一一个 bean 赋值给 userDao 这个成员变量;后者,Spring 会调用 setUserDao 方法来将 UserDao 类型的唯一一个 bean 装配到 userDao 这个属性。
Spring 框架会在运行时自动地查找符合要求的对象并将它们注入到相应的位置中,从而实现依赖注入的功能。
使用 @Autowired 注释可以帮助我们避免手动创建对象的繁琐过程,减少代码量,提高代码的可读性和可维护性。同时,使用 @Autowired 注释还可以方便地解决对象依赖关系的问题,使得代码之间的耦合度更低,更加灵活。
Web 层,一般对应控制器,实现请求和响应的控制->对应 web 包
Service 服务层,用来实现业务逻辑->对应 service 包
DAO 数据访问层,用于对数据库的增删改查操作(CRUD),也称 repository 仓库层->对应 mapper 包
Entity 实体层:用于实现对数据库表的映射(也称Bean、POJO)->对应 model 包
Service 层是 Web 层与 DAO 层的桥梁
登录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @RequestMapping(value = "/login") public Map<String, Object> login (@RequestBody User user) { User userObj = userService.login(user); if (userObj == null ) { return MyResult.getResultMap(420 , "账号或密码错误" ); } else { String token = TokenProcessor.getInstance().makeToken(); userService.saveUser(token, userObj); return MyResult.getResultMap(200 , "登录成功" , new HashMap <String, String>(){{ put("token" , token); }}); } }
@RequestBody:接收前端提交的 json 格式数据
1 2 3 4 5 6 7 8 9 10 11 package com.wangpeng.bms.service.impl;@Service public class UserServiceImpl implements UserService { @Resource private UserMapper userMapper; @Resource private RedisTemplate<Object, Object> redisTemplate; }
@Autowired 与 @Resource 都可以用来装配 bean. 都可以写在字段上,或写在 setter 方法上。
@Autowired 默认按类型装配(这个注解是属于 spring 的),默认情况下必须要求依赖对象必须存在,如果要允许 null 值,可以设置它的 required 属性为 false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合 @Qualifier 注解进行使用,如下:
1 2 @Autowired() @Qualifier("baseDao") private BaseDao baseDao;
@Resource(这个注解属于J2EE的),默认安照名称进行装配,名称可以通过 name 属性进行指定, 如果没有指定 name 属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。 当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。
1 2 @Resource(name="baseDao") private BaseDao baseDao;
参考:https://www.cnblogs.com/mr-wuxiansheng/p/6392190.html
登录包含的接口函数
匹配条件:用户名、密码以及管理员与否
在 redis 中匹配到相应用户则返回其 User 对象。
1 2 3 4 @Override public User login (User user) { return userMapper.selectByUsernameAndPasswordAndIsAdmin(user.getUsername(), user.getUserpassword(), user.getIsadmin()); }
根据当前用户登录成功获取的 token 进行保存(token 的时效为一小时)
1 2 3 4 5 6 7 @Override public void saveUser (String token, User user) { redisTemplate.setKeySerializer(new StringRedisSerializer ()); redisTemplate.opsForValue().set(token, user, 1 , TimeUnit.HOURS); }
用户 token 在时效期内且,用户为登出(删除 token)的话,用户无需再次输入密码登录。
在一次登录后可以看到 cookie 已被创建,用户的会话都记录在 redis 上
也可以在 redis 管理工具上看到生成的用户 session(退出登录,session 随之删除)
涉及的 mapper 方法及对应 xml 配置
对 sql 语句参数映射赋值
1 2 3 User selectByUsernameAndPasswordAndIsAdmin (@Param("username") String username, @Param("password") String password, @Param("isAdmin") Byte isAdmin) ;
1 2 3 4 5 6 7 8 9 <select id ="selectByUsernameAndPasswordAndIsAdmin" resultMap ="BaseResultMap" > select <include refid ="Base_Column_List" /> from user where userName = #{username} and userPassword = #{password} and isAdmin = #{isAdmin} limit 1 </select >
@Param:这个注解是为 SQL 语句中参数赋值而服务的。
@Param 的作用就是给参数命名,比如在 mapper 里面某方法 A(int id),当添加注解后 A(@Param(“userId”) int id),也就是说外部想要取出传入的 id 值,只需要取它的参数名 userId 就可以了。将参数值传入 SQL 语句中,通过 #{userId} 进行取值给 SQL 的参数赋值。
参考:https://blog.csdn.net/Sunshineoe/article/details/114697944
响应信息类
@RequestBody:接收前端提交的 json 格式数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class MyResult { public static HashMap<String, Object> getResultMap (Integer status, String message) { return new HashMap <String, Object>() { { put("status" , status); put("message" , message); put("timestamp" , System.currentTimeMillis()); } }; } public static HashMap<String, Object> getResultMap (Integer status, String message, Object data) { return new HashMap <String, Object>() { { put("status" , status); put("message" , message); put("data" , data); put("timestamp" , System.currentTimeMillis()); } }; } public static HashMap<String, Object> getListResultMap (Integer status, String message, Integer count, Object data) { return new HashMap <String, Object>() { { put("code" , status); put("message" , message); put("count" , count); put("data" , data); } }; } }
查看用户信息 1 2 3 4 5 6 7 8 9 10 @RequestMapping(value = "/info") public Map<String, Object> info (String token) { User user = userService.getUser(token); if (user == null ) { return MyResult.getResultMap(420 , "获取用户信息失败" ); } else { return MyResult.getResultMap(200 , "获取用户信息成功" , user); } }
查看用户信息包含的接口函数
1 2 3 4 5 @Override public User getUser (String token) { return (User) redisTemplate.opsForValue().get(token); }
退出登录 1 2 3 4 5 6 @RequestMapping(value = "/logout") public Map<String, Object> logout (String token) { userService.removeUser(token); return MyResult.getResultMap(200 , "退出登录成功" ); }
退出登录包含的接口函数
1 2 3 4 5 @Override public void removeUser (String token) { redisTemplate.delete(token); }
删除时效内的 token ,需要重新登录。
退出后重载 redis 数据库,可以看到对应的 session 会话记录也被随之删除。
注册 1 2 3 4 @RequestMapping(value = "/register") public Integer register (String username, String password) { return userService.register(username, password); }
注册包含的接口函数
1 2 3 4 5 6 7 8 9 10 11 @Override public Integer register (String username, String password) { User tmp = userMapper.selectByUsername(username); if (tmp != null ) return 0 ; User user = new User (); user.setUsername(username); user.setUserpassword(password); user.setIsadmin((byte )0 ); return userMapper.insertSelective(user); }
涉及的 mapper 方法及对应 xml 配置
查找重复用户名
1 User selectByUsername (String username) ;
1 2 3 4 5 6 7 <select id ="selectByUsername" resultMap ="BaseResultMap" > select <include refid ="Base_Column_List" /> from user where userName = #{username} limit 1 </select >
新建用户
1 int insertSelective (User record) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <insert id ="insertSelective" parameterType ="com.wangpeng.bms.model.User" > insert into user <trim prefix ="(" suffix =")" suffixOverrides ="," > <if test ="userid != null" > userId, </if > <if test ="username != null" > userName, </if > <if test ="userpassword != null" > userPassword, </if > <if test ="isadmin != null" > isAdmin, </if > </trim > <trim prefix ="values (" suffix =")" suffixOverrides ="," > <if test ="userid != null" > #{userid,jdbcType=INTEGER}, </if > <if test ="username != null" > #{username,jdbcType=VARCHAR}, </if > <if test ="userpassword != null" > #{userpassword,jdbcType=VARCHAR}, </if > <if test ="isadmin != null" > #{isadmin,jdbcType=TINYINT}, </if > </trim > </insert >
prefix:在 trim 标签内 sql 语句加上前缀。 suffix:在 trim 标签内 sql 语句加上后缀。
prefixOverrides:指定去除多余的前缀内容 suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=”,”,去除 trim 标签内 sql 语句多余的后缀”,”。
就结合上述代码为例
如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:
如果匹配的只是第一个和第二个条件又会怎样?这条 SQL 会是这样:
1 insert into user (userId,userName) values (#{userid,jdbcType= INTEGER },#{username,jdbcType= VARCHAR })
假设没有指定:suffixOverrides=","
执行的 sql 语句也许是这样的:(观察括号中最后面逗号)
1 insert into user (userId,userName,) values (#{userid,jdbcType= INTEGER },#{username,jdbcType= VARCHAR },)
显然是错误的,因为最后括号前的逗号多余了
参考:
https://blog.csdn.net/tianynnb/article/details/120718085
https://blog.csdn.net/qq_39715000/article/details/124025498
修改密码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RequestMapping(value = {"/alterPassword", "reader/alterPassword"}) public Integer alterPassword (Integer userid, String username, Byte isadmin, String oldPassword, String newPassword) { User userObj = new User (); userObj.setUserid(userid); userObj.setUsername(username); userObj.setUserpassword(oldPassword); userObj.setIsadmin(isadmin); User user = userService.login(userObj); if (user == null ) { return 0 ; } else { userService.setPassword(userObj.getUserid(), newPassword); return 1 ; } }
修改密码的接口函数
1 2 3 4 5 6 7 8 @Override public void setPassword (Integer id, String password) { User user = new User (); user.setUserid(id); user.setUserpassword(password); userMapper.updateByPrimaryKeySelective(user); }
涉及的 mapper 方法及对应 xml 配置
1 int updateByPrimaryKeySelective (User record) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <update id ="updateByPrimaryKeySelective" parameterType ="com.wangpeng.bms.model.User" > update user <set > <if test ="username != null" > userName = #{username,jdbcType=VARCHAR}, </if > <if test ="userpassword != null" > userPassword = #{userpassword,jdbcType=VARCHAR}, </if > <if test ="isadmin != null" > isAdmin = #{isadmin,jdbcType=TINYINT}, </if > </set > where userId = #{userid,jdbcType=INTEGER} </update >
略
获得数量 1 2 3 4 @GetMapping(value = "/getCount") public Integer getCount () { return userService.getCount(); }
获得数量的接口函数
1 2 3 4 @Override public Integer getCount () { return userMapper.selectCount(); }
涉及的 mapper 方法及对应 xml 配置
查询 user 表中的记录数
1 2 3 <select id ="selectCount" resultType ="int" > select count(*) from user </select >
count() 查询的是记录数,当无结果返回时,默认返回 0,如果直接把 count(*) 换成 * ,当无结果时,则返回空。
查询所有用户 1 2 3 4 @GetMapping(value = "/queryUsers") public List<User> queryUsers () { return userService.queryUsers(); }
获得数量的接口函数
1 2 3 4 @Override public List<User> queryUsers () { return userMapper.selectAll(); }
涉及的 mapper 方法及对应 xml 配置
1 2 3 4 5 <select id ="selectAllByLimit" resultMap ="BaseResultMap" > select <include refid ="Base_Column_List" /> from user limit #{begin}, #{size} </select >
分页查询用户 params: {page, limit, username} 1 2 3 4 5 6 7 @GetMapping(value = "/queryUsersByPage") public Map<String, Object> queryUsersByPage (@RequestParam Map<String, Object> params) { MyUtils.parsePageParams(params); int count = userService.getSearchCount(params); List<User> users = userService.searchUsersByPage(params); return MyResult.getListResultMap(0 , "success" , count, users); }
@RequestParam:接收来自 requestHeader 即请求头中的参数。
MyUtils 类详情
1 2 3 4 5 6 7 8 9 10 11 public class MyUtils { public static void parsePageParams (Map<String, Object> params) { int page = Integer.parseInt((String) params.get("page" )); int size = Integer.parseInt((String) params.get("limit" )); params.put("begin" , (page - 1 ) * size); params.put("size" , size); } }
MyResult 类用于创建浏览器响应信息
获得查询结果数量的接口函数
1 2 3 public int getSearchCount (Map<String, Object> params) { return userMapper.selectCountBySearch(params); }
涉及的 mapper 方法及对应 xml 配置
1 int selectCountBySearch (Map<String, Object> params) ;
1 2 3 4 5 6 7 8 <select id ="selectCountBySearch" resultType ="int" > select count(*) from user <where > <if test ="username != null and username != '' " > and userName like concat('%',#{username},'%') </if > </where > </select >
获得查询用户集的接口函数
1 2 3 4 @Override public List<User> searchUsersByPage (Map<String, Object> params) { return userMapper.selectBySearch(params); }
涉及的 mapper 方法及对应 xml 配置
1 List<User> selectBySearch (Map<String, Object> params) ;
1 2 3 4 5 6 7 8 9 10 <select id ="selectBySearch" resultMap ="BaseResultMap" > select userId, userName, userPassword, isAdmin from user <where > <if test ="username != null and username != '' " > and userName like concat('%',#{username},'%') </if > </where > limit #{begin}, #{size} </select >
SQL LIKE 子句用于在 WHERE 语句中进行模糊匹配,它会将给定的匹配模式和某个字段进行比较,匹配成功则选取,否则不选取。
通配符
说明
百分号(%)
代表零个、一个或者多个任意的字符。
sql concat() 函数用于将几个字符串连接起来,形成一个单一的字符串
用在此处表示匹配 userName 中包含有 username 的记录。
添加用户 1 2 3 4 @PostMapping(value = "/addUser") public Integer addUser (@RequestBody User user) { return userService.addUser(user); }
获得添加用户的接口函数
1 2 3 4 @Override public Integer addUser (User user) { return userMapper.insertSelective(user); }
涉及的 mapper 方法及对应 xml 配置
1 int insertSelective (User record) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <insert id ="insertSelective" parameterType ="com.wangpeng.bms.model.User" > insert into user <trim prefix ="(" suffix =")" suffixOverrides ="," > <if test ="userid != null" > userId, </if > <if test ="username != null" > userName, </if > <if test ="userpassword != null" > userPassword, </if > <if test ="isadmin != null" > isAdmin, </if > </trim > <trim prefix ="values (" suffix =")" suffixOverrides ="," > <if test ="userid != null" > #{userid,jdbcType=INTEGER}, </if > <if test ="username != null" > #{username,jdbcType=VARCHAR}, </if > <if test ="userpassword != null" > #{userpassword,jdbcType=VARCHAR}, </if > <if test ="isadmin != null" > #{isadmin,jdbcType=TINYINT}, </if > </trim > </insert >
略
删除用户 1 2 3 4 @DeleteMapping(value = "/deleteUser") public Integer deleteUser (@RequestBody User user) { return userService.deleteUser(user); }
获得删除用户的接口函数
1 2 3 4 5 @Override public Integer deleteUser (User user) { if (user.getUserid() == 1 ) return 0 ; return userMapper.deleteByPrimaryKey(user.getUserid()); }
涉及的 mapper 方法及对应 xml 配置
1 int deleteByPrimaryKey (Integer userid) ;
1 2 3 4 <delete id ="deleteByPrimaryKey" parameterType ="java.lang.Integer" > delete from user where userId = #{userid,jdbcType=INTEGER} </delete >
略
删除一些用户 1 2 3 4 @DeleteMapping(value = "/deleteUsers") public Integer deleteUsers (@RequestBody List<User> users) { return userService.deleteUsers(users); }
获得删除一些用户的接口函数
1 2 3 4 5 6 7 8 @Override public Integer deleteUsers (List<User> users) { int count = 0 ; for (User user : users) { count += deleteUser(user); } return count; }
涉及的 mapper 方法及对应 xml 配置
详情见==删除用户==
更新用户 1 2 3 4 @RequestMapping(value = "/updateUser") public Integer updateUser (@RequestBody User user) { return userService.updateUser(user); }
获得更新用户的接口函数
1 2 3 4 @Override public Integer updateUser (User user) { return userMapper.updateByPrimaryKeySelective(user); }
涉及的 mapper 方法及对应 xml 配置
1 int updateByPrimaryKeySelective (User record) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <update id ="updateByPrimaryKeySelective" parameterType ="com.wangpeng.bms.model.User" > update user <set > <if test ="username != null" > userName = #{username,jdbcType=VARCHAR}, </if > <if test ="userpassword != null" > userPassword = #{userpassword,jdbcType=VARCHAR}, </if > <if test ="isadmin != null" > isAdmin = #{isadmin,jdbcType=TINYINT}, </if > </set > where userId = #{userid,jdbcType=INTEGER} </update >
MyBatis 在生成 update 语句时若使用 if 标签,如果前面的 if 没有执行,则可能导致有多余逗号的错误。
使用 set 标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。
在 MyBatis 中,<set>
标签用于动态生成 UPDATE
语句的 SET
子句。使用 <set>
标签可以方便地根据需要动态生成 SET
子句。
下面是 <set>
标签的基本用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <update id ="updateUser" > update user <set > <if test ="name != null" > name = #{name}, </if > <if test ="email != null" > email = #{email}, </if > <if test ="phone != null" > phone = #{phone}, </if > </set > where id = #{id} </update >
在上面的示例中,<set>
标签包含了多个子标签,每个子标签都代表了一个要更新的列。在子标签中使用了 <if>
条件判断语句,当对应的属性不为 null
时,才会将对应的列包含在 SET
子句中。
例如,如果传入的参数中 name
属性为 null
,那么 name
列就不会被包含在 SET
子句中。最终生成的 SQL 语句可能是这样的:
1 2 3 4 update user set email = 'john@example.com' , phone = '123456789' where id = 1
注意,在 SET
子句中,每个列之间需要用逗号 ,
分隔开。在最后一个列后面可以不加逗号。
采用MyBatis访问数据库 创建数据库
在上一步我们已经创建了名为 book_manager 的数据库
添加数据库相关依赖
在 pom.xml 文件中,已经整合了该部分的相关依赖
配置mysql连接
application.properties 中配置 mysql 连接配置和 redis 连接配置,怎么配置前面也已经写过
创建模型类 (在bean中,此处包名为 model)
创建 Mapper 接口
创建Mapper配置文件:XML文件
直接存放在 mapper 包下,免去了在 application.properties 中配置对应的 xml 文件路径
创建 Mapper 配置文件:XML文件
UserMapper.xml
1 2 3 4 5 6 <?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.wangpeng.bms.mapper.UserMapper" > ... </mapper >
定义一个 sql 标签,id 唯一(包含 user 表内所有字段)
1 2 3 <sql id ="Base_Column_List" > userId, userName, userPassword, isAdmin </sql >
定义映射关系
1 2 3 4 5 6 7 <resultMap id ="BaseResultMap" type ="com.wangpeng.bms.model.User" > <id column ="userId" jdbcType ="INTEGER" property ="userid" /> <result column ="userName" jdbcType ="VARCHAR" property ="username" /> <result column ="userPassword" jdbcType ="VARCHAR" property ="userpassword" /> <result column ="isAdmin" jdbcType ="TINYINT" property ="isadmin" /> </resultMap >
匹配 user 表中的字段
映射 mapper 中的接口方法
1 2 3 4 5 6 7 8 9 10 <select id ="selectByPrimaryKey" parameterType ="java.lang.Integer" resultMap ="BaseResultMap" > select <include refid ="Base_Column_List" /> from user where userId = #{userid,jdbcType=INTEGER} </select > <delete id ="deleteByPrimaryKey" parameterType ="java.lang.Integer" > delete from user where userId = #{userid,jdbcType=INTEGER} </delete >
参考:
https://www.cnblogs.com/openmind-ink/p/14034713.html
https://blog.csdn.net/weixin_44279264/article/details/126294791
select/insert/update/delete:表示对应数据的操作
id:对应 mapper 接口的方法名
parameterType:参数类型(要写完整包名)
resultMap:指定字段设置映射关系
resultType:函数对应返回值类型(要写完整包名)
注:
1、resultmap:mybatis 中在查询进行 select 映射的时候,返回类型可以用 resultType,也可以用 resultMap。 2、resulttype:resultType 是直接表示返回类型的,而 resultMap 则是对外部 ResultMap 的引用,但是 resultType 跟 resultMap 不能同时存在。
…
创建服务
放在 service 包(UserService 接口 + UserServiceImpl 实现)
UserService
接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface UserService { User login (User user) ; void saveUser (String token, User user) ; User getUser (String token) ; void removeUser (String token) ; Integer register (String username, String password) ; void setPassword (Integer id, String password) ; Integer getCount () ; List<User> queryUsers () ; int getSearchCount (Map<String, Object> searchParam) ; List<User> searchUsersByPage (Map<String, Object> params) ; Integer addUser (User user) ; Integer deleteUser (User user) ; Integer deleteUsers (List<User> users) ; Integer updateUser (User user) ; }
用于定义一些业务功能
UserServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 @Service public class UserServiceImpl implements UserService { @Resource private UserMapper userMapper; @Resource private RedisTemplate<Object, Object> redisTemplate; @Override public User login (User user) { return userMapper.selectByUsernameAndPasswordAndIsAdmin(user.getUsername(), user.getUserpassword(), user.getIsadmin()); } ... }
==详情见上文==
实现接口,用于调用 Mapper 完成业务功能