• Java SpringBoot VI


    Java SpringBoot VI

    1. 使用Druid数据库连接池

    Druid数据库连接是阿里巴巴团队研发的,在Spring Boot项目中,如果需要显式的指定使用此连接池,首先,需要在项目中添加依赖:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.20</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当添加了此依赖,在项目中需要应用时,需要在配置文件中指定spring.datasource.type属性,取值为以上依赖项的jar包中的DruidDataSource类型的全限定名。

    例如,在yml中配置为:

    # Spring系列框架的配置
    spring:
      # 连接数据库的相关配置
      datasource:
        # 使用的数据库连接池类型
        type: com.alibaba.druid.pool.DruidDataSource
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 编写持久层(数据访问层)代码

    数据持久化:在开发领域中,讨论数据时,通常指定是正在执行或处理的数据,这些数据都是在内存中的,而内存(RAM)的特征包含”一旦断电,数据将全部丢失“,为了让数据永久保存下来,通常会将数据存储到能够永久存储数据的介质中,通常是计算机的硬盘,硬盘上的数据都是以文件的形式存在的,所以,当需要永久保存数据时,可以将数据存储到文本文件中,或存储到XML文件中,或存储到数据库中,这些保存的做法就是数据持久化,而文本文件、XML文件都不利于实现增删改查中的所有数据访问操作,而数据库是实现增删改查这4种操作都比较便利的,所以,一般在讨论数据持久化时,默认指的都是使用数据库存储数据。

    在项目中,会将代码(各类、接口)划分一些层次,各层用于解决不同的问题,其中,持久层就是用于解决数据持久化问题的,甚至,简单来说,持久层对应的就是数据库编程的相关文件或代码。

    目前,使用Mybatis技术实现持久层编程,需要:

    • 编写一次性的基础配置
      • 使用@MapperScan指定接口所在的Base Package
      • 指定配置SQL语句的XML文件的位置
    • 编写每个数据访问功能的代码
      • 在接口中添加必须的抽象方法
        • 可能需要创建相关的POJO类
      • 在XML文件中配置抽象方法映射的SQL语句

    关于一次性的配置,@MapperScan注解需要添加在配置类上,有2种做法:

    • 直接将此注解添加在启动类上,因为启动类本身也是配置类
    • 自行创建配置类,在此配置类上添加@MapperScan

    如果采用以上的第2种做法,则应该在src\main\java的根包下,创建config.MybatisConfig类,并在此类使用@MapperScan注解:

    package cn.tedu.boot.demo.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @MapperScan("cn.tedu.boot.demo.mapper")
    public class MybatisConfig {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    另外,关于指定配置SQL语句的XML文件的位置,需要在application.yml(或application.properties)中配置mybatis.mapper-locations属性,例如:

    # Mybatis相关配置
    mybatis:
      # 用于配置SQL语句的XML文件的位置
      mapper-locations: classpath:mapper/*.xml
    
    • 1
    • 2
    • 3
    • 4

    基于以上的配置值,还应该在src/main/resources下自行创建名为mapper的文件夹。

    至此,关于使用Mybatis实现数据库编程的一次性配置结束!

    接下来,可以使用任何你已知的Mybatis使用方式实现所需的数据访问。

    目前,设定目标为:最终实现”添加管理员账号“的功能。则在数据访问层需要做到:

    • 插入管理员数据
      • 创建cn.tedu.boot.demo.entity.Admin
      • cn.tedu.boot.demo.mapper包(不存在,则创建)下创建AdminMapper接口,并在接口中声明int insert(Admin admin);方法
      • src/main/resources/mapper文件夹下通过粘贴得到AdminMapper.xml文件,在此文件中配置与以上抽象方法映射的SQL语句
      • 编写完成后,应该及时测试,测试时,推荐在src/test/java的根包下创建mapper.AdminMapperTests测试类,并在此类中编写测试方法
    • 根据用户名查询管理员数据
      • 后续,在每次插入数据之前,会调用此功能进行查询,以此保证”重复的用户名不会被添加到数据库中“
        • 即便在数据表中用户名已经添加了unique,但是,不应该让程序执行到此处
      • AdminMapper接口中添加Admin getByUsername(String username);方法
      • AdminMapper.xml文件中添加与以上抽象方法映射的SQL语句
      • 编写完成后,应该及时测试
    • 其它问题暂不考虑,例如在ams_admin中,其实phoneemail也是设置了unique的,如果完整的实现,则还需要添加根据phone查询管理员的功能,和根据email查询管理员的功能,在不实现这2个功能的情况下,后续进行测试和使用时,应该不使用重复的phoneemail值来测试或执行

    3. 关于业务逻辑层(Service层)

    业务逻辑层是被Controller直接调用的层(Controller不允许直接调用持久层),通常,在业务逻辑层中编写的代码是为了保证数据的完整性和安全性,使得数据是随着我们设定的规则而产生或发生变化。

    通常,在业务逻辑层的代码会由接口和实现类组件,其中,接口被视为是必须的

    • 推荐使用基于接口的编程方式
    • 部分框架在处理某些功能时,会使用基于接口的代理模式,例如Spring JDBC框架在处理事务时

    在接口中,声明抽象方法时,仅以操作成功为前提来设计返回值类型(不考虑失败),如果业务在执行过程可能出现某些失败(不符合所设定的规则),可以通过抛出异常来表示!

    关于抛出的异常,通常是自定义的异常,并且,自定义异常通常是RuntimeException的子类,主要原因:

    • 不必显式的抛出或捕获,因为业务逻辑层的异常永远是抛出的,而控制器层会调用业务逻辑层,在控制器层的Controller中其实也是永远抛出异常的,这些异常会通过Spring MVC统一处理异常的机制进行处理,关于异常的整个过程都是固定流程,所以,没有必要显式抛出或捕获
    • 部分框架在处理某些事情时,默认只对RuntimeException的子孙类进行识别并处理,例如Spring JDBC框架在处理事务时

    所以,在实际编写业务逻辑层之前,应该先规划异常,例如先创建ServiceException类:

    package cn.tedu.boot.demo.ex;
    
    public class ServiceException extends RuntimeException {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接下来,再创建具体的对应某种“失败”的异常,例如,在添加管理员时,可能因为“用户名已经存在”而失败,则创建对应的UsernameDuplicateException异常:

    package cn.tedu.boot.demo.ex;
    
    public class UsernameDuplicateException extends ServiceException {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    另外,当插入数据时,如果返回的受影响行数不是1时,必然是某种错误,则创建对应的插入数据异常:

    package cn.tedu.boot.demo.ex;
    
    public class InsertException extends ServiceException {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于抽象方法的参数,应该设计为客户端提交的数据类型或对应的封装类型,不可以是数据表对应的实体类型!如果使用封装的类型,这种类型在类名上应该添加某种后缀,例如DTO或其它后缀,例如:

    package cn.tedu.boot.demo.pojo.dto;
    
    public class AdminAddNewDTO implements Serializable {
        private String username;
        private String password;
        private String nickname;
        private String avatar;
        private String phone;
        private String email;
        private String description;
        // Setters & Getters
        // hashCode(), equals()
        // toString()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后,在cn.tedu.boot.demo.service包下声明接口及抽象方法:

    package cn.tedu.boot.demo.service;
    
    public interface IAdminService {
        void addNew(AdminAddNewDTO adminAddNewDTO);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    并在以上service包下创建impl子包,再创建AdminServiceImpl类:

    package cn.tedu.boot.demo.service.impl;
    
    @Service // @Component, @Controller, @Repository
    public class AdminServiceImpl implements IAdminService {
        
        @Autowired
        private AdminMapper adminMapper;
        
        @Override
        public void addNew(AdminAddNewDTO adminAddNewDTO) {
            // 通过参数获取用户名
            // 调用adminMapper的Admin getByUsername(String username)方法执行查询
            // 判断查询结果是否不为null
            // -- 是:表示用户名已经被占用,则抛出UsernameDuplicateException
            
            // 通过参数获取原密码
            // 通过加密方式,得到加密后的密码encodedPassword
            // 暂时不加密,写为String encodedPassword = adminAddNewDTO.getPassword();
            
            // 创建当前时间对象now > LocalDateTime.now()
            
            // 创建Admin对象
            // 补全Admin对象的属性值:通过参数获取username,nickname……
            // 补全Admin对象的属性值:password > encodedPassword
            // 补全Admin对象的属性值:isEnable > 1
            // 补全Admin对象的属性值:lastLoginIp > null
            // 补全Admin对象的属性值:loginCount > 0
            // 补全Admin对象的属性值:gmtLastLogin > null
            // 补全Admin对象的属性值:gmtCreate > now
            // 补全Admin对象的属性值:gmtModified > now
            // 调用adminMapper的insert(Admin admin)方法插入管理员数据,获取返回值
            
            // 判断以上返回的结果是否不为1,抛出InsertException异常
        }
        
    }
    
    • 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

    以上业务代码的实现为:

    package cn.tedu.boot.demo.service.impl;
    
    import cn.tedu.boot.demo.entity.Admin;
    import cn.tedu.boot.demo.ex.InsertException;
    import cn.tedu.boot.demo.ex.UsernameDuplicateException;
    import cn.tedu.boot.demo.mapper.AdminMapper;
    import cn.tedu.boot.demo.pojo.dto.AdminAddNewDTO;
    import cn.tedu.boot.demo.service.IAdminService;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.time.LocalDateTime;
    
    @Service
    public class AdminServiceImpl implements IAdminService {
    
        @Autowired
        private AdminMapper adminMapper;
    
        @Override
        public void addNew(AdminAddNewDTO adminAddNewDTO) {
            // 通过参数获取用户名
            String username = adminAddNewDTO.getUsername();
            // 调用adminMapper的Admin getByUsername(String username)方法执行查询
            Admin queryResult = adminMapper.getByUsername(username);
            // 判断查询结果是否不为null
            if (queryResult != null) {
                // 是:表示用户名已经被占用,则抛出UsernameDuplicateException
                throw new UsernameDuplicateException();
            }
    
            // 通过参数获取原密码
            String password = adminAddNewDTO.getPassword();
            // 通过加密方式,得到加密后的密码encodedPassword
            String encodedPassword = password;
    
            // 创建当前时间对象now > LocalDateTime.now()
            LocalDateTime now = LocalDateTime.now();
    
            // 创建Admin对象
            Admin admin = new Admin();
            // 补全Admin对象的属性值:通过参数获取username,nickname……
            admin.setUsername(username);
            admin.setNickname(adminAddNewDTO.getNickname());
            admin.setAvatar(adminAddNewDTO.getAvatar());
            admin.setPhone(adminAddNewDTO.getPhone());
            admin.setEmail(adminAddNewDTO.getEmail());
            admin.setDescription(adminAddNewDTO.getDescription());
            // 以上这些从一个对象中把属性赋到另一个对象中,还可以使用:
            // BeanUtils.copyProperties(adminAddNewDTO, admin);
            // 补全Admin对象的属性值:password > encodedPassword
            admin.setPassword(encodedPassword);
            // 补全Admin对象的属性值:isEnable > 1
            admin.setIsEnable(1);
            // 补全Admin对象的属性值:lastLoginIp > null
            // 补全Admin对象的属性值:loginCount > 0
            admin.setLoginCount(0);
            // 补全Admin对象的属性值:gmtLastLogin > null
            // 补全Admin对象的属性值:gmtCreate > now
            admin.setGmtCreate(now);
            // 补全Admin对象的属性值:gmtModified > now
            admin.setGmtModified(now);
            // 调用adminMapper的insert(Admin admin)方法插入管理员数据,获取返回值
            int rows = adminMapper.insert(admin);
    
            // 判断以上返回的结果是否不为1,抛出InsertException异常
            if (rows != 1) {
                throw new InsertException();
            }
        }
    
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    以上代码未实现对密码的加密处理!关于密码加密,相关的代码应该定义在别的某个类中,不应该直接将加密过程编写在以上代码中,因为加密的代码需要在多处应用(添加用户、用户登录、修改密码等),并且,从分工的角度上来看,也不应该是业务逻辑层的任务!所以,在cn.tedu.boot.demo.util(包不存在,则创建)下创建PasswordEncoder类,用于处理密码加密:

    package cn.tedu.boot.demo.util;
    
    @Component
    public class PasswordEncoder {
        
        public String encode(String rawPassword) {
            return "aaa" + rawPassword + "aaa";
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    完成后,需要在AdminServiceImpl中自动装配以上PasswordEncoder,并在需要加密时调用PasswordEncoder对象的encode()方法。

    我是将军;我一直都在,。!

  • 相关阅读:
    Python和Excel的完美结合:常用操作汇总(案例详析)
    第三章 SpringBoot构造流程源码分析
    MT3030 天梯赛
    C/C++输出整数 2020年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
    Python使用psycopg2读取PostgreSQL的geometry字段出现二进制乱码
    ES6中的Promise对象
    Git使用入门
    Vue首屏加载过慢出现白屏的六种优化方案
    复习背诵整理版
    3.Python_创建型模式_抽象工厂模式
  • 原文地址:https://blog.csdn.net/letterljhx/article/details/126919675