Mybatis01https://blog.csdn.net/ZUIHENderen46/article/details/83552252
1. MyBatis的使用
1.1. 获取新插入的数据的id
如果在插入数据时,就需要实时获取新数据的id,首先,在配置XML时,<insert>
节点需要添加2个属性的配置:
<insert id="xx" parameterType="xx.xx.xx.xx.User"
useGeneratedKeys="true"
keyProperty="id">
</insert>
以上配置中,useGeneratedKeys
表示需要获取自动生成的主键(通常都是自动递增的id),keyProperty
表示当获取了主键的值(id的值),该值将被封装到哪个属性中,即插入数据时的参数User
类型中的id
属性!
所以,具体的效果是,当尝试调用Integer reg(User user)
方法执行插入时,指定了以上配置后,MyBatis会将新插入的数据的id
封装到user
参数对象中,所以:
userMapper.reg(user);
System.out.println(user.getId());
1.2. MyBatis中的动态SQL
1.2.1. 概念
MyBatis支持动态SQL,具体的表现是:在配置SQL语句时,可以使用一些简单的逻辑代码,例如if、foreach……使得最终编译出来的SQL语句并不是固定的,而是可能随着参数发生变化的!
1.2.2. 使用foreach一次删除多条数据
一次删除多条数据的SQL语句大致是:
DELETE FROM 表名 WHERE id IN (?,?,?)
而实际应用时,id的列表是由用户进行选取得到的,所以,对于开发者而言,根本就不知道id的列表到底是多少!
针对这样的需求,应该使用动态SQL中的foreach,遍历id列表,得到SQL语句 中例如(?,?,?)
这个部分即可。
假设在t_user
表中实现这样的操作,首先,还是在接口中声明抽象方法:
Integer delete(List<Integer> ids);
然后,在配置的SQL映射中:
<delete id="delete">
DELETE FROM
t_user
WHERE
id IN (
<foreach collection="list"
item="id"
separator=",">
#{id}
</foreach>
)
</delete>
在<foreach>
节点中,collection
表示需要遍历的目标,可以是List
集合,也可以是数据,当该节点对应的方法只有1个参数时,该属性的取值是list
或array
,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param
注解中确定的名称);item
属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>
节点内部也会使用该名称表示变量;seperator
表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7
这样的格式,则该属性的值为逗号;还有open
和close
属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写SQL语句时没有指定IN
关键字后面的括号时,可以添加open="(" close=")"
这2项配置。
1.2.3. 使用if选择性的更新数据
在有些更新数据的场景里,可能存在:一次性可以更新同一个用户的多项属性数据,但是,如果没有提交其中的某个数据,则该数据不发生变化!例如:在修改个人资料页面中,还可以有“新密码”输入框,如果不想修改密码,则不填写即可。
针对这样的应用需求,可以添加抽象方法:
Integer changeInfo(User user);
然后,配置SQL映射:
UPDATE
t_user
SET
<if test="password != null">
password=#{password},
</if>
phone=#{phone},
email=#{email},
birthday=#{birthday}
WHERE
id=#{id}
2. 业务
2.1. 定位
在MVC的程序设计中,把整个项目的核心划分出了M、V、C这3大部分。
其中,V表示View,即视图,通常指的是HTML或JSP文件,用于显示界面,为用户提供操作入口,用户可以在界面进行点击、输入、选择等操作;
而C表示Controller,即控制器,在原生的JavaEE技术中,指的是Servlet,在SSM框架中,指的是Controller类,主要作用是接收请求,并给予响应,但是,对数据本身并不作实质的处理;
还有M表示Model,即数据模型,具体的指对数据的处理操作,为了更加明确的划分数据的逻辑与执行的操作,所以,在实际编程时,Model表现为Service和Dao/Mapper的组合,其中,Service用于处理业务,而Dao/Mapper处理数据的增删改查;
因此,定位为Service的类,通常称之为业务逻辑类,主要用于设计业务的流程与逻辑,即:什么时候允许做什么、先做什么再做什么……
有了业务逻辑后,更加易于保证数据的安全。
有了业务逻辑后,持久层(Dao/Mapper)也就不再关心业务,只是单纯的实现增删改查即可,而不用考虑能不能增加或修改或删除等。
2.2. 用户注册的业务
2.2.1. 分析业务逻辑
用户注册中就可以有:用户名必须是唯一的,如果尝试注册的用户名已经被占用,则不允许注册!
2.2.2. 实现
首先,持久层(DAO/Mapper)必须具备:根据用户名查询用户信息,以判断某个用户名是否被占用;注册,将用户数据添加到数据表中。
然后,创建业务接口cn.tedu.mybatis.service.IUserService
,并添加抽象方法:
public interface IUserService {
User reg(User user);
}
接下来,创建以上接口的实现类cn.tedu.mybatis.service.impl.UserServiceImpl
,后续对应的查询、注册功能都需要通过持久层(UserMapper
)来完成,所以,在这个类中,需要UserMapper的对象,可以声明为成员变量,然后通过Spring为其自动装配来注入值,当然,要使用自动装配机制,首先得保证整个类(UserServiceImpl
)是能够被Spring管理的,所以,当前类还需要添加@Service
注解,并且,在Spring的配置文件中开启组件扫描,要能够扫到当前类所在的包!
然后再实现以上抽象方法:
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
public User reg(User user) {
// 根据user.getUsername()查询用户信息
String username = user.getUsername();
User result
= userMapper.getUserByUsername(username);
// 判断查询结果是否为null
if (result == null) {
// 是:没有被占用,则允许注册
userMapper.reg(user);
// 返回
return user;
} else {
// 否:已经被占用,则不允许注册
throw new RuntimeException(
"您尝试注册的用户名(" + username + ")已经被占用!");
}
}
}
最后,测试:
public class TestUserService {
@Test
public void reg() {
// 加载Spring的配置文件,获取Spring容器
AbstractApplicationContext ac
= new ClassPathXmlApplicationContext(
"spring-service.xml", "spring-dao.xml");
// 获取所需的对象:IUserService
IUserService service
= ac.getBean("userService", IUserService.class);
// 测试执行
try {
User user = new User("苍教师", "123456");
User result = service.reg(user);
System.out.println("注册成功:" + result);
} catch (RuntimeException e) {
System.out.println("注册失败:" + e.getMessage());
}
// 释放资源
ac.close();
}
}
2.3. 用户登录的业务
2.3.1. 分析
用户需要提交用户名、密码才可以登录,在验证时,应该根据用户名查询用户信息,如果存在,则验证用户输入的密码,与刚才查询结果中的密码是否匹配,如果还能够匹配,则登录成功,如果用户名匹配的数据不存在,或密码不匹配,则登录失败。
2.3.2. 实现登录验证
在IUserService
接口中添加新的抽象方法,表示用户登录:
User login(String username, String password);
首先,方法名可以是简单易懂的,使用login
即可,方法参数取决于用户提交的数据,或必要的数据,则可以是String username, String password
,返回值只考虑登录成功后所需要得到的结果,则设计为User
类型,后续,当控制器(Controller
)调用这个方法,并且成功登录后,可以通过该返回值获取到当前用户的id
、用户名甚至头像等等数据,存放到Session中,以用于判断用户已经登录及显示相关数据!
在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!
为了保证抛出多种异常能形成程序中的分支,应该为不同错误创建不同的异常类,也就是需要自定义异常,例如:UserNotFoundException
、PasswordNotMatchException
,这些异常都应该存放于业务包的ex
子包中,同时,为了便于统一处理异常(至于是仔细处理还是统一处理,都是控制器决定,在写业务层,应该为各种做法都提供基础)还自定义一个ServiceException
,作为当前项目中所有的业务层可能抛出的异常的基类,并且是继承自RuntimeException
的。
然后,在UserServiceImpl
实现类中实现以上方法:
public User login(String username, String password) {
// 根据用户名查询用户信息
// 判断与用户名匹配的用户信息是否存在
// 是:存在,判断参数密码和查询到的用户信息中的密码是否匹配
// -- 是:匹配,登录成功,将查询到的用户信息作为返回值
// -- 否:不匹配,抛出异常:密码错误
// 否:不存在,抛出异常:用户名不存在
}
2.4. 暂时小结
-
业务逻辑类,主要用于设计业务的流程与逻辑,即:什么时候允许做什么、先做什么再做什么……
-
在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!
-
应该创建
ServiceException
类表示当前项目的业务异常的基类,当前项目中所有的业务异常都应该是这个类的子孙类! -
在业务方法的实现中,凡是认定为“错误”或“失败”,均抛出对应的异常对象,并在抛出的对象描述错误信息,如果没有合适的异常类,则创建新的异常类即可!
尝试完成“修改密码”的整套流程,即在界面可以输入原密码、新密码,提交到控制器,控制器会调用业务层的方法,在业务层中,会判断原密码是否正确,正确的情况下,会调用持久层来更新密码。
持久层分析
通常,在开发某个功能时,应该先处理持久层,也就是能对数据进行存取,对于当前功能而言,在持久层需要:将新密码更新到数据表中,判断原密码是否正确这2个功能,整个功能是用户登录后才允许执行的,通常,登录后会在Session中存入当前用户的id,所以,无论是修改密码,还是检查原密码,都应该是基于id来筛选数据的。
所以,在持久层对应的SQL语句大致是:
UPDATE t_user SET password=? WHERE id=?
SELECT id, username, password, phone, email, birthday FROM t_user WHERE id=?
所以,在持久层的接口UserMapper.java
中添加2个抽象方法:
Integer changePassword(
@Param("id") Integer id,
@Param("password") String password);
User getUserById(Integer id);
然后在UserMapper.xml
中配置以上2个抽象方法的SQL映射。
业务层分析
业务层的主要职责是设计业务流程和业务逻辑,所以,应该在业务层判断用户输入的原始密码是否正确,正确的情况下,允许更新密码,错误的情况下,抛出异常。
所以,应该先在业务层接口IUserService.java
中添加抽象方法:
void changePassword(
Integer id, String oldPassword, String newPassword);
然后,在UserServiceImpl.java
实现类中实现以上方法:
public void changePassword(
Integer id, String oldPassword, String newPassword) {
// 根据id获取用户数据
// 判断是否获取到数据
// 是:查询到用户数据,则验证原密码,即:判断查到的数据中的密码与oldPassword是否一致
// -- 是:原密码正确,则允许修改密码,调用修改密码的方法
// -- 判断修改操作受影响的行数是否“不等于1”
// -- -- 是:受影响的行数不是1,视为修改失败,抛出异常:UpdateException
// -- 否:原密码错误,抛出异常:密码错误
// 否:没有与id匹配的数据,抛出异常:用户不存在
}
控制器层分析
控制器层的主要职责是接收请求,给予响应,在执行过程中,如果需要实现某些功能,则应该调用对应的业务层方法来完成!
当前“修改密码”的功能涉及2种请求,分别是“显示修改密码页面”和“处理用户提交的修改密码的数据”这2种。
关于显示修改密码页面:
请求路径:/user/change_password.do
请求类型:GET
请求参数:无
响应方式:转发到change_password.jsp
是否拦截:是,登录拦截,但无须修改配置
所以,添加处理请求的方法:
@RequestMapping("/change_password.do")
public String showChangePassword() {
return "change_password";
}
关于处理用户提交的修改密码的数据:
请求路径:/user/handle_change_password.do
请求类型:POST
请求参数:oldPassword(old_password), newPassword(new_password),HttpSession,ModelMap
响应方式:如果成功,则重定向到user/info.do,如果失败,则转发到error
是否拦截:是,登录拦截,但无须修改配置
所以,添加处理请求的方法:
@RequestMapping(value="/handle_change_password.do",
method=RequestMethod.POST)
public String handleChangePassword(
@RequestParam("old_password") String oldPassword,
@RequestParam("new_password") String newPassword,
HttpSession session, ModelMap modelMap) {
// try {
// 从session中获取uid
// 调用业务层对象的changePassword(id, oldPassword, newPassword)方法执行修改密码
// 如果能执行到此处,表示没有发生异常,则修改成功,则重定向到user/info.do
// } catch (UserNotFoundException e) {
// 向modelMap中封装错误信息
// 执行转发:error
// } catch (PasswordNotMatchException e) {
// 向modelMap中封装错误信息
// 执行转发:error
// } catch (UpdateException e) {
// 向modelMap中封装错误信息
// 执行转发:error
// }
}
前端页面
复制原有的reg.jsp
,得到change_password.jsp
,将表单提交到的位置(处理表单的URL)、原密码与新密码输入框的name属性修改为控制器要求的参数名称。