SSM + Shiro 整合 (2)- 实现 Spring 集成 MyBatis

23 篇文章 0 订阅
17 篇文章 3 订阅

项目源码:https://github.com/weimingge14/Shiro-project
演示地址:http://liweiblog.duapp.com/Shiro-project/login

本节目标:实现 Spring 集成 MyBatis 的整合,并且完成单元测试。

步骤1:创建数据库表(数据库脚本附在本文后,或者在本项目的 GitHub 仓库中下载)

步骤2:编写 MyBatis 的配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置全局属性-->
    <settings>
        <!--使用 jdbc 的 getGeneratekeys 获取自增主键值-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--使用列别名替换别名  默认true
        select name as title form table;
        -->
        <setting name="useColumnLabel" value="true"/>

        <!--开启驼峰命名转换Table:create_time到 Entity(createTime)-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

</configuration>

同样地,该配置文件如何编写,文档约束片段也应该到 MyBatis 的官方网站或官方文档上去拷贝。

步骤3:创建 Spring 关于数据访问层的配置文件 spring/spring-dao.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!-- 该配置文件是 Dao 层的配置文件,实现了 Spring 与 MyBatis 框架的整合 -->

    <!-- 1、将数据库连接参数化,
    建议键值带 jdbc. 前缀,否则如果使用 username ,Spring 框架会优先使用系统变量 username
    而不会使用我们在配置文件中使用的键 username -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 配置数据源 -->
    <!-- 使用的数据库连接池产品是 Driud  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- 数据源驱动类可不写, Druid 默认会自动根据URL识别DriverClass -->
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.pool.init}"/>
        <property name="minIdle" value="${jdbc.pool.minIdle}"/>
        <property name="maxActive" value="${jdbc.pool.maxActive}"/>

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000"/>

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000"/>

        <property name="validationQuery" value="${jdbc.testSql}"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小(Oracle使用)
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> -->

        <!-- 配置监控统计拦截的filters -->
        <property name="filters" value="stat"/>
    </bean>

    <!-- MyBatis 集成 Spring 必须配置的项 1 、配置 SqlSessionFactory 实例 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!-- 扫描 sql 配置文件,即 mapper 对应的 xml 文件 -->
        <property name="mapperLocations" value="classpath:mappers/*.xml"/>
        <!-- 扫描 entity 包,这样在 mapper 中就可以使用简单类名,多个用 ; 隔开 -->
        <property name="typeAliasesPackage" value="com/liwei/shiro/model"/>
        <!-- 配置 MyBatis 全局配置文件 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- MyBatis 集成 Spring 必须配置的项 2,可以不配置 id  -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 给出须要被扫描的 Dao 接口-->
        <property name="basePackage" value="com.liwei.shiro.dao"/>
        <!-- 注入 SqlSessionFactory -->
        <!-- 这是推荐配置的项,不要去配置 sqlSessionFactory ,已经被弃用了-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

</beans>

步骤4:创建 MyBatis 的接口和配置文件(xml文件)

说明:接口代码应该是上面的配置文件中 MapperScannerConfigurer 部分的 basePackage 属性所指明的包下的接口。
接口的实例化由 Spring 帮助我们完成。

public interface UserDao {

    Integer add(User user);

    Integer update(User user);

    Integer delete(Integer id);

    User load(Integer id);

    List<User> listUser();

    User loadByUserName(String username);

    /**
     * 根据角色 id 查询所有是该角色的用户列表
     * @param rid
     * @return
     */
    List<User> listByRole(Integer rid);

    List<Resource> listAllResources(Integer uid);

    List<String> listRoleSnByUser(Integer uid);

    List<Role> listUserRole(Integer uid);
}

步骤5:创建与上面接口对应的 xml 文件

<?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.liwei.shiro.dao.UserDao">

    <insert id="add" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO t_user(username,`password`,nickname,`status`)
        VALUES(#{username},#{password},#{nickname},#{status});
    </insert>

    <!-- 动态更新对象的写法 -->
    <update id="update" parameterType="User">
        UPDATE t_user
        <set>
            <if test="username != null">
                username = #{username},
            </if>
            <if test="password != null">
                `password` = #{password},
            </if>
            <if test="nickname != null">
                nickname = #{nickname},
            </if>
            <if test="status != null">
                `status` = #{status}
            </if>
        </set>
        WHERE
          id = #{id}
    </update>

    <delete id="delete" parameterType="int">
        DELETE FROM t_user
        where id = #{id}
    </delete>


    <select id="load" parameterType="int" resultType="User">
        SELECT
            id,
            username,
            `password`,
            nickname,
            `status`
        FROM
            t_user
        WHERE
            id = #{id}
    </select>

    <select id="listUser" resultType="User">
        SELECT
            id,
            username,
            `password`,
            nickname,
            `status`
        FROM
            t_user
    </select>

    <select id="loadByUserName" parameterType="string" resultType="User">
        SELECT
            id,
            username,
            `password`,
            nickname,
            `status`
        FROM
            t_user
        WHERE
            username = #{username}
    </select>

    <select id="listByRole" parameterType="int" resultType="User">
        SELECT
            tu.id,
            tu.username,
            tu.`password`,
            tu.nickname,
            tu.`status`
        FROM
            t_user tu
        LEFT JOIN t_user_role tur ON tu.id = tur.user_id
        LEFT JOIN t_role tr ON tur.role_id = tr.id
        WHERE tr.id =#{rid}
    </select>

    <!-- 根据用户 id 查询这个用户拥有的所有资源(这里的资源就代表权限) -->
    <select id="listAllResources" resultType="Resource" parameterType="int">
        SELECT tr.`id`,tr.`name`,tr.`permission`,tr.`url`
        FROM `t_resource` tr
        LEFT JOIN `t_role_resource` trr ON tr.`id` = trr.`resource_id`
        LEFT JOIN `t_user_role` tur ON trr.`role_id` = tur.role_id
        WHERE tur.user_id = #{uid}
    </select>

    <!-- 根据用户 id 查询用户所具有的角色字符串表示 -->
    <select id="listRoleSnByUser" parameterType="int" resultType="string">
        SELECT
            tr.sn
        FROM `t_role` tr
        LEFT JOIN `t_user_role` tur ON tr.`id` = tur.role_id
        LEFT JOIN `t_user` tu ON tur.`user_id` = tu.id
        WHERE tu.`id` = #{uid}
    </select>

    <!-- 根据用户 id 查询用户所具有的角色对象表示 -->
    <select id="listUserRole" parameterType="int" resultType="Role">
        SELECT
            tr.`id`,
            tr.`name`,
            tr.`sn`
            FROM `t_role` tr
            LEFT JOIN `t_user_role` tur ON tr.`id` = tur.role_id
            LEFT JOIN `t_user` tu ON tur.`user_id` = tu.id
        WHERE tu.`id` = #{uid}
    </select>

</mapper>

我们可以发现 Dao 接口的代码和 mapper(xml)文件其实是一致的。
mapper(xml)文件 <mapper namespace="com.liwei.shiro.dao.UserDao">
中 namespace 属性值就是对应的 Dao 接口文件的全类名。

步骤 6:编写 Service 层代码

接口代码

public interface IUserService {

    /**
     * 添加单个用户
     * @param user
     */
    Integer add(User user);

    /**
     * 批量添加用户角色关联表数据
     * @param user
     * @param rids
     */
    void add(User user,List<Integer> rids);

    /**
     * 根据 user_id 删除用户数据
     * @param id
     */
    void delete(int id);

    /**
     * // TODO: 2016/9/18   应该设置为一个事务
     * 更新用户数据
     * 1、更新用户基本信息
     * 2、更新用户所属角色
     *    (1)先删除所有的角色
     *    (2)再添加绑定的角色
     * @param user
     * @param rids
     */
    void update(User user,List<Integer> rids);

    /**
     * 更新单个用户信息
     * @param user
     * @return
     */
    Integer update(User user);

    /**
     * 根据主键 id 加载用户对象
     * @param id
     * @return
     */
    User load(int id);

    /**
     * 根据用户名加载用户对象(用于登录使用)
     * @param username
     * @return
     */
    User loadByUsername(String username);

    /**
     * 登录逻辑
     * 1、先根据用户名查询用户对象
     * 2、如果有用户对象,则继续匹配密码
     * 如果没有用户对象,则抛出异常
     * @param username
     * @param password
     * @return
     */
    User login(String username,String password);

    /**
     * 查询所有的用户对象列表
     * @return
     */
    List<User> list();

    /**
     * 根据角色 id 查询是这个角色的所有用户
     * @param id
     * @return
     */
    List<User> listByRole(int id);

    /**
     * 查询指定用户 id 所拥有的权限
     * @param uid
     * @return
     */
    List<Resource> listAllResource(int uid);

    /**
     * 查询指定用户所指定的角色字符串列表
     * @param uid
     * @return
     */
    List<String> listRoleSnByUser(int uid);

    /**
     * 查询指定用户所绑定的角色列表
     * @param uid
     * @return
     */
    List<Role> listUserRole(int uid);

}

实现类代码:

@Service
public class UserService implements IUserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleDao roleDao;

    /**
     * 返回新插入用户数据的主键
     * @param user
     * @return
     */
    @Override
    public Integer add(User user) {
        // 使用用户名作为盐值,MD5 算法加密
        user.setPassword(ShiroKit.md5(user.getPassword(),user.getUsername()));
        userDao.add(user);
        Integer userId = user.getId();
        return userId;
    }

    /**
     * 为单个用户设置多个角色
     * @param user
     * @param rids
     */
    @Override
    public void add(User user, List<Integer> rids) {
        Integer userId = this.add(user);
        roleDao.addUserRoles(userId,rids);
    }

    /**
     * 根据 user_id 删除用户数据
     * @param id
     */
    @Override
    public void delete(int id) {
        userDao.delete(id);
    }

    /**
     * // TODO: 2016/9/18   应该设置为一个事务
     * 更新用户数据
     * 1、更新用户基本信息
     * 2、更新用户所属角色
     *    (1)先删除所有的角色
     *    (2)再添加绑定的角色
     * @param user
     * @param rids
     */
    @Override
    public void update(User user, List<Integer> rids) {
        Integer userId = user.getId();
        roleDao.deleteUserRoles(userId);
        roleDao.addUserRoles(userId,rids);
        this.update(user);
    }

    /**
     * 更新单个用户信息
     * @param user
     * @return
     */
    @Override
    public Integer update(User user) {
        String password = user.getPassword();
        if(password!=null){
            user.setPassword(ShiroKit.md5(user.getPassword(),user.getUsername()));
        }
        return userDao.update(user);
    }

    /**
     * 根据主键 id 加载用户对象
     * @param id
     * @return
     */
    @Override
    public User load(int id) {
        return userDao.load(id);
    }

    /**
     * 根据用户名加载用户对象(用于登录使用)
     * @param username
     * @return
     */
    @Override
    public User loadByUsername(String username) {
        return userDao.loadByUserName(username);
    }

    /**
     * 登录逻辑
     * 1、先根据用户名查询用户对象
     * 2、如果有用户对象,则继续匹配密码
     * 如果没有用户对象,则抛出异常
     * @param username
     * @param password
     * @return
     */
    @Override
    public User login(String username, String password) {
        User user = userDao.loadByUserName(username);
        if(user == null){
            // 抛出对象不存在异常
            // TODO: 2016/9/18  应该使用 Shiro 框架的登录方式,暂时先这样
            logger.debug("用户名不存在");
            throw new UnknownAccountException("用户名和密码不匹配");
        }else if(false){
            // !user.getPassword().equals(password)
            logger.debug("密码错误");
            // 抛出密码不匹配异常
            throw new IncorrectCredentialsException("用户名和密码不匹配");
        }else if(user.getStatus() == 0){
            throw new LockedAccountException("用户已经被锁定,请联系管理员启动");
        }
        return user;
    }

    /**
     * 查询所有的用户对象列表
     * @return
     */
    @Override
    public List<User> list() {
        return userDao.listUser();
    }

    /**
     * 根据角色 id 查询是这个角色的所有用户
     * @param id
     * @return
     */
    @Override
    public List<User> listByRole(int id) {
        return userDao.listByRole(id);
    }

    /**
     * 查询指定用户 id 所拥有的权限
     * @param uid
     * @return
     */
    @Override
    public List<Resource> listAllResource(int uid) {
        return userDao.listAllResources(uid);
    }

    /**
     * 查询指定用户所指定的角色字符串列表
     * @param uid
     * @return
     */
    @Override
    public List<String> listRoleSnByUser(int uid) {
        return userDao.listRoleSnByUser(uid);
    }

    /**
     * 查询指定用户所绑定的角色列表
     * @param uid
     * @return
     */
    @Override
    public List<Role> listUserRole(int uid) {
        return userDao.listUserRole(uid);
    }

}

说明:在 Service 实现类中可以直接注入 Dao 层。即可以在 private UserDao userDao; 上标注 @Autowire 注解。

步骤7:编写测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
        "classpath:spring/spring-service.xml",
        "classpath:spring/spring-dao.xml"})
public class UserServiceTest {

    private static final Logger logger = LoggerFactory.getLogger(UserServiceTest.class);

    @Autowired
    private IUserService userService;


    @Test
    public void add() throws Exception {
        User user = new User();
        user.setUsername("zhouguang");
        user.setPassword("666666");
        user.setNickname("周光1");
        user.setStatus(1);
        userService.add(user);
        logger.debug("返回自增长的主键:" + user.getId());
    }

}

附:数据库脚本:

drop database ssm_shiro;
# 创建数据库 ssm_shiro
CREATE DATABASE ssm_shiro DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
# 使用数据库 ssm_shiro
USE ssm_shiro;
# 创建数据表 t_user
CREATE TABLE t_user(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '用户 ID',
  username VARCHAR(30) NOT NULL comment '用户名',
  `password` VARCHAR(32) NOT NULL comment '密码',
  nickname VARCHAR(30) NOT NULL comment '昵称',
  `status` TINYINT not null comment '状态:1 启用,2 禁用'
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

# 创建数据用于测试
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('admin','a66abb5684c45962d887564f08346e8d','超级管理员',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('dev','c43812121e594f158520698ba706118f','开发工程师',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('test','47ec2dd791e31e2ef2076caf64ed9b3d','测试工程师',1);
INSERT INTO t_user(username,`password`,nickname,`status`)
VALUES('doc','5afd1e481507a2a181decc3860b32d15','文档工程师',1);

# 创建数据表 t_role
# name 字段用于显示给人看, sn 字段用在代码中做角色匹配
CREATE TABLE t_role(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '角色表 ID',
  `name` VARCHAR(20) NOT NULL comment '角色名称',
  sn VARCHAR(20) NOT NULL comment '角色字符串'
)engine=innodb auto_increment=1 DEFAULT charset=utf8 comment='角色信息表';

# 创建数据用于测试
INSERT INTO t_role(`name`,`sn`) VALUES('管理员','admin'),('开发工程师','dev'),('测试工程师','test'),('文档工程师','doc');

# 创建数据表 t_user_role
CREATE TABLE t_user_role(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '用户角色关联表 ID',
  user_id TINYINT NOT NULL,
  role_id TINYINT NOT NULL
)engine=innodb auto_increment=1 charset=utf8 comment='用户角色关联表';

# 创建数据用于测试
INSERT INTO `t_user_role`(`user_id`,`role_id`)
VALUES(1,1),(2,2),(3,3),(4,4);

# 创建资源表 t_resource
# 资源在本项目中的含义就是 "权限"
CREATE TABLE t_resource(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '资源 ID',
  `name` VARCHAR(20) NOT NULL comment '资源名称,一般是中文名称(给人看的)',
  permission VARCHAR(40) NOT NULL comment '资源权限字符串,一般是 Shiro 默认的通配符表示(给人看的)',
  url VARCHAR(40) NOT NULL comment '资源 url 表示,我们设计的系统让 Shiro 通过这个路径字符串去匹配浏览器中显示的路径'
)engine=innodb auto_increment=1 charset=utf8 comment='资源表';

# 创建数据用于测试
INSERT INTO t_resource(`name`,permission,url)
VALUES('系统管理','admin:*','/admin/**'),
('用户管理','user:*','/admin/user/**'),
('用户添加','user:add','/admin/user/add'),
('用户删除','user:delete','/admin/user/delete'),
('用户修改','user:update','/admin/user/update'),
('用户查询','user:list','/admin/user/list'),
('用户资源查询','user:resources:*','/admin/user/resources/*'),
('角色管理','role:*','/admin/role/**'),
('角色添加','role:add','/admin/role/add'),
('角色删除','role:delete','/admin/role/delete'),
('角色修改','role:update','/admin/role/update'),
('角色查询','role:list','/admin/role/list'),
('角色资源查询','role:resources:*','/admin/role/resources/*'),
('资源管理','resource:*','/admin/resource/**'),
('资源增加','resource:add','/admin/resource/add'),
('资源删除','resource:delete','/admin/resource/delete'),
('资源修改','resource:update','/admin/resource/update'),
('资源查询','resource:list','/admin/resource/list');


# 创建角色资源关联表
CREATE TABLE t_role_resource(
  id TINYINT PRIMARY KEY AUTO_INCREMENT comment '角色资源关联 ID',
  role_id TINYINT not null comment '角色 id',
  resource_id TINYINT not null comment '资源 id'
)engine=innodb auto_increment=1 charset=utf8 comment='角色资源关联表';

# 创建数据用于测试
INSERT INTO t_role_resource(role_id,resource_id)
VALUES(1,1),
(2,3),(2,5),(2,6),(2,7),(2,9),(2,11),(2,12),(2,13),(2,15),(2,17),(2,18),
(3,6),(3,7),(3,8),(3,14),
(4,6),(4,7),(4,12),(4,18);
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值