项目实战(SpringJDBC框架事务管理(续),管理员角色处理)

37. 基于Spring JDBC框架的事务管理(续)

在执行数据访问操作时,数据库有一个“自动提交”的机制。

事务的本质是会先将“自动提交”关闭,当业务方法执行结束之后,再一次性“提交”。

在事务中,涉及几个概念:

  • 开启事务:BEGIN
  • 提交事务:COMMIT
  • 回滚事务:ROLLBACK

在基于Spring JDBC的程序设计中,通过@Transactional注解即可使得业务方法是事务性的,其实现过程大致是:

开启事务
try {
    执行业务方法
    提交事务
} catch (RuntimeException e) {
    回滚事务
}

可以看到,Spring JDBC框架在处理事务时,默认将根据RuntimeException进行回滚!

提示:可以配置@Transactional注解的rollbackForrollbackForClassName属性来指定回滚的异常类型,即根据其它类型的异常来回滚,例如:

@Transactional(rollbackFor = {IOException.class})
@Transactional(rollbackForClassName = {}"java.io.IOException"})

另外,还可以通过noRollbackFornoRollbackForClassName属性用于指定不回滚的异常!

建议在业务方法中执行了任何增、删、改操作后,都获取受影响的行数,并判断此值是否符合预期,如果不符合,应该及时抛出RuntimeException或其子孙类异常!

补充:Spring JDBC框架在实现事务管理时,使用到了Spring AOP技术及基于接口的代理模式,由于使用了基于接口的代理模式,所以,如果将@Transactional注解添加在实现类中自定义的方法(不是重写的接口中的抽象方法)上,是错误的做法!

最后,可自行补充相关知识点:事务的ACID特性,事务的传播,事务的隔离。

38. 完善删除管理员

由于添加管理员时,在ams_admin_role表中插入了数据,在删除管理员时,也应该将ams_admin_role表中对应的数据删除!

删除ams_admin_role表中某管理员的数据需要执行的SQL语句大致是:

DELETE FROM ams_admin_role WHERE admin_id=?

则在AdminRoleMapper接口中添加抽象方法:

/**
 * 根据管理员id删除管理员与角色的关联数据
 *
 * @param adminId 管理员id
 * @return 受影响的行数
 */
int deleteByAdminId(Long adminId);

并在AdminRoleMapper.xml中配置SQL语句:

<!-- int deleteByAdminId(Long adminId); -->
<delete id="deleteByAdminId">
    DELETE FROM ams_admin_role WHERE admin_id=#{adminId}
</delete>

完成后,在AdminRoleMapperTests中编写并执行测试:

@Test
void testDeleteByAdminId() {
    Long adminId = 7L;
    int rows = mapper.deleteByAdminId(adminId);
    log.debug("删除数据完成!受影响的行数={}", rows);
}

当Mapper层的开发完成后,在AdminServiceImpl中的delete()方法最后补充调用以上方法即可:

// 执行删除此管理员与角色的关联数据
rows = adminRoleMapper.deleteByAdminId(id);
if (rows < 1) {
    String message = "删除管理员失败,服务器忙,请稍后再次尝试!";
    log.warn(message);
    throw new ServiceException(ServiceCode.ERR_DELETE, message);
}

完成后,删除管理员的功能将暂时全部完成!

但是,需要注意:不要使用错误的测试数据进行测试!

39. 显示角色列表

在“添加管理员”的界面中,需要将角色列表显示出来,则用户(软件的使用者)可以在界面上选择“添加管理员”时此管理员的角色。

关于“显示角色列表”的Mapper层

需要执行的SQL语句大致是:

SELECT * FROM ams_role ORDER BY sort DESC, id

则需要在pojo.vo包中创建RoleListItemVO类:

package cn.tedu.csmall.passport.pojo.vo;

import lombok.Data;

import java.io.Serializable;

/**
 * 角色的列表项的VO类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Data
public class RoleListItemVO implements Serializable {

    private Long id;
    private String name;
    private String description;
    private Integer sort;

}

然后,在mapper包中创建RoleMapper接口,并添加抽象方法:

package cn.tedu.csmall.passport.mapper;

import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 处理角色数据的Mapper接口
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Repository
public interface RoleMapper {

    /**
     * 查询角色列表
     *
     * @return 角色列表
     */
    List<RoleListItemVO> list();

}

然后,在src/main/resources/mapper文件夹下,通过复制粘贴得到RoleMapper.xml文件,在此文件中配置以上抽象方法映射的SQL语句:

<?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="cn.tedu.csmall.passport.mapper.RoleMapper">

    <!-- List<RoleListItemVO> list(); -->
    <select id="list" resultMap="ListResultMap">
        SELECT
            <include refid="ListQueryFields" />
        FROM
            ams_role
        ORDER BY
            sort DESC, id
    </select>

    <sql id="ListQueryFields">
        <if test="true">
            id, name, description, sort
        </if>
    </sql>

    <resultMap id="ListResultMap" type="cn.tedu.csmall.passport.pojo.vo.RoleListItemVO">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="description" property="description"/>
        <result column="sort" property="sort"/>
    </resultMap>

</mapper>

完成后,在src/test/java下的根包下创建mapper.RoleMapperTests测试类,在此类中编写并执行测试:

package cn.tedu.csmall.passport.mapper;

import cn.tedu.csmall.passport.pojo.entity.Admin;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@Slf4j
@SpringBootTest
public class RoleMapperTests {

    @Autowired
    RoleMapper mapper;

    @Test
    void testList() {
        List<?> list = mapper.list();
        System.out.println("查询列表完成,列表中的数据的数量=" + list.size());
        for (Object item : list) {
            System.out.println(item);
        }
    }

}

关于“显示角色列表”的Service层

service包下创建IRoleService接口,并在接口中添加抽象方法:

/**
 * 处理角色数据的业务接口
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Transactional
public interface IRoleService {

    /**
     * 查询角色列表
     *
     * @return 角色列表
     */
    List<RoleListItemVO> list();

}

然后,在service.impl包下创建RoleServiceImpl实现类,实现以上接口,并在类上添加@Service注解,在类中声明RoleMapper属性并自动装配:

/**
 * 处理角色数据的业务实现类
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Service
public class RoleServiceImpl implements IRoleService {

    @Autowired
    RoleMapper roleMapper;

    @Override
    public List<RoleListItemVO> list() {
        log.debug("开始处理【查询角色列表】的业务");
        return roleMapper.list();
    }

}

完成后,在src/test/java下的根包下创建service.RoleServiceTests测试类,在此类中编写并执行测试:

package cn.tedu.csmall.passport.service;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@Slf4j
@SpringBootTest
public class RoleServiceTests {

    @Autowired
    IRoleService service;

    @Test
    void testList() {
        List<?> list = service.list();
        System.out.println("查询列表完成,列表中的数据的数量=" + list.size());
        for (Object item : list) {
            System.out.println(item);
        }
    }

}

关于“显示角色列表”的Controller层

controller包下创建RoleController类,在类上添加@RestController@RequestMapping("/roles")注解,在类中声明并自动装配IRoleService属性,然后,在类中添加处理请求的方法:

package cn.tedu.csmall.passport.controller;

import cn.tedu.csmall.passport.pojo.vo.RoleListItemVO;
import cn.tedu.csmall.passport.service.IRoleService;
import cn.tedu.csmall.passport.web.JsonResult;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@Api(tags = "02. 角色管理模块")
@RestController
@RequestMapping("/roles")
public class RoleController {

    @Autowired
    IRoleService roleService;

    public RoleController() {
        log.info("创建控制器类:RoleController");
    }

    // http://localhost:9081/roles
    @ApiOperation("查询角色列表")
    @ApiOperationSupport(order = 420)
    @GetMapping("")
    public JsonResult<List<RoleListItemVO>> list() {
        log.debug("开始处理【查询角色列表】的请求");
        return JsonResult.ok(roleService.list());
    }

}

完成后,重启项目,可以通过在线API文档测试访问。

40. 启动或禁用管理员

管理的启用状态是通过数据表中的enable字段的值来控制的,所以,启用、禁用功能的本质是修改此字段的值!

在开发功能时,应该在Mapper层开发一个能够修改所有字段的值的功能,则此功能可以适用于当前表的任何修改功能!

关于Mapper层

先在AdminMapper接口中添加抽象方法:

/**
 * 根据管理员id修改管理员的数据
 *
 * @param admin 封装了管理员id和新的数据的对象
 * @return 受影响的行数
 */
int updateById(Admin admin);

然后在AdminMapper.xml中配置SQL:

<!-- int updateById(Admin admin); -->
<update id="updateById">
    UPDATE ams_admin
    <set>
        <if test="username != null">
            username=#{username},
        </if>
        <if test="password != null">
            password=#{password},
        </if>
        <if test="nickname != null">
            nickname=#{nickname},
        </if>
        <if test="avatar != null">
            avatar=#{avatar},
        </if>
        <if test="phone != null">
            phone=#{phone},
        </if>
        <if test="email != null">
            email=#{email},
        </if>
        <if test="description != null">
            description=#{description},
        </if>
        <if test="enable != null">
            enable=#{enable},
        </if>
        <if test="lastLoginIp != null">
            last_login_ip=#{lastLoginIp},
        </if>
        <if test="loginCount != null">
            login_count=#{loginCount},
        </if>
        <if test="gmtLastLogin != null">
            gmt_last_login=#{gmtLastLogin},
        </if>
    </set>
    WHERE id=#{id}
</update>

完成后,在AdminMapperTests中编写并执行测试:

@Test
void testUpdateById() {
    Admin data = new Admin();
    data.setId(1L);
    data.setUsername("新的测试数据名称");

    int rows = mapper.updateById(data);
    System.out.println("修改数据完成,受影响的行数=" + rows);
}

关于Service层

IAdminService接口中添加抽象方法,抽象方法可以是:

void updateEnableById(Long id, Integer enable);

提示:也可以将以上方法的2个参数进行封装。

但是,更建议声明为:

/**
 * 启用管理员
 *
 * @param id 管理员的id
 */
void setEnable(Long id);

/**
 * 禁用管理员
 *
 * @param id 管理员的id
 */
void setDisable(Long id);

然后,在AdminServiceImpl中实现:

@Override
public void setEnable(Long id) {
    updateEnableById(id, 1);
}

@Override
public void setDisable(Long id) {
    updateEnableById(id, 0);
}

private void updateEnableById(Long id, Integer enable) {
    String[] tips = {"禁用", "启用"};
    log.debug("开始处理【{}管理员】的业务,参数:{}", tips[enable], id);
    // 检查尝试访问的数据是否存在
    AdminStandardVO queryResult = adminMapper.getStandardById(id);
    if (queryResult == null) {
        String message = tips[enable] + "管理员失败,尝试访问的数据不存在!";
        log.debug(message);
        throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
    }

    // 判断状态是否冲突(当前已经是目标状态)
    if (queryResult.getEnable() == enable) {
        String message = tips[enable] + "管理员失败,管理员账号已经处于" + tips[enable] + "状态!";
        log.debug(message);
        throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
    }

    // 准备执行更新
    Admin admin = new Admin();
    admin.setId(id);
    admin.setEnable(enable);
    int rows = adminMapper.updateById(admin);
    if (rows != 1) {
        String message = tips[enable] + "管理员失败,服务器忙,请稍后再次尝试!";
        log.warn(message);
        throw new ServiceException(ServiceCode.ERR_UPDATE, message);
    }
}

完成后,在AdminServiceTests中编写并执行测试:

@Test
void testSetEnable() {
    Long id = 500L;

    try {
        service.setEnable(id);
        System.out.println("启用管理员成功!");
    } catch (ServiceException e) {
        System.out.println(e.getMessage());
    }
}

@Test
void testSetDisable() {
    Long id = 5L;

    try {
        service.setDisable(id);
        System.out.println("禁用管理员成功!");
    } catch (ServiceException e) {
        System.out.println(e.getMessage());
    }
}

关于Controller层

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

专注摸鱼的汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值