Mybatis--02

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个参数时,该属性的取值是listarray,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param注解中确定的名称);item属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>节点内部也会使用该名称表示变量;seperator表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7这样的格式,则该属性的值为逗号;还有openclose属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写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中,以用于判断用户已经登录及显示相关数据!

在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!

为了保证抛出多种异常能形成程序中的分支,应该为不同错误创建不同的异常类,也就是需要自定义异常,例如:UserNotFoundExceptionPasswordNotMatchException,这些异常都应该存放于业务包的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属性修改为控制器要求的参数名称。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值