整合常用技术框架之 JPA 和 Redis

整合 Spring Data JPA

首先,添加依赖,代码如下:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

然后,添加配置文件,代码如下:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useSSL=false
    username: root
    password: root
    ##初始化连接数
    initialSize: 5
    ##最小连接数
    minIdle: 5
    ##最大连接数
    maxActive: 20
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

spring.jpa.hibernate.ddl-auto 属性可以取以下值,其含义如下:

  • create:每次加载 Hibernate 时都会删除上一次生成的表,然后根据你的实体类再重新生成新表;
  • create-drop:每次加载 Hibernate 时根据实体类生成表,但是 sessionFactory 一关闭,表就自动删除;
  • update:最常用的属性(推荐属性),第一次加载 Hibernate 时根据实体类会自动建立起表的结构(前提是先建立好数据库),以后加载 Hibernate 时根据实体类自动更新表结构。即使表结构改变了,但表中的行仍然存在,不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,要等应用第一次运行起来后才会创建;
  • validate:每次加载 Hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但会插入新值。

接着,我们添加实体类,代码如下:

@Table(name = "user")
@Entity
public class User {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "user_name",length = 16,nullable = false)
    private String name;

    @Column(name = "age",nullable = false)
    private Integer age;

    @Transient
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

注意,当在实体类中定义的属性不需要映射到对应数据库表中时,只需要在实体类属性字段上添加 @transient 注解即可。

Spring Boot 整合 JPA 访问数据库非常方便,这也是其被广泛用于操作数据库的主要原因。开发过程中,通常我们会直接继承 JpaRepository 接口实现对数据库表的操作。继承该接口,操作数据库时,只需要让接口中定义的方法名称满足一定的规则,JPA 框架便能根据方法名自动解析产生 SQL 语句。示例代码如下:

public interface UserRepository extends JpaRepository<User,Integer> {
    User findByName(String name);
}

如上定义的 findByName 方法经过 JPA 框架解析之后,会自动生成一条按照用户名称查询用户的 SQL 语句,并发送到数据库执行查询操作。

编写 userService 接口和实现类:

public interface IUserService {
    User queryUserByName(String name);

    void addUser(User user);
}

@Service("userService")
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserRepository userDao;

    @Override
    public User queryUserByName(String name) {
        return userDao.findByName(name);
    }

    @Override
    public void addUser(User user) {
        userDao.save(user);
    }
}

编写 Controller:

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    private IUserService userService;

    @RequestMapping("/queryUserByName")
    public User queryUserByName(@RequestParam("name") String name) {
        return userService.queryUserByName(name);
    }

    @RequestMapping(value = "/addUser", method = RequestMethod.GET)
    public String queryUserByName(@RequestParam("name") String name, @RequestParam("age") Integer age) {
        userService.addUser(new User(name, age));
        return "Success";
    }
}

利用 Postman 向数据库 user 表中插入三条数据:

enter image description here

扫描二维码关注公众号,回复: 4667958 查看本文章

在 Postman 输入请求地址:

http://localhost:8080/user/queryUserByName?name=huangchaojun

对查询接口进行测试:

enter image description here

看了上面的实例,相信各位已了解了 JPA 功能的强大之处,不过上面只是一个简单的实例,官网提供了 JPA 规范命名方法名到 SQL 语句映射的实例,如下图所示:

enter image description hereenter image description here

没错,看了上面官网给出的 JPA 持久层方法名称到 SQL 语句的映射之后,相信各位又进一步了解了 JPA 的强大之处。不过对于一些特殊功能,如排序、分页等,需要开发者做一些特殊处理。下面分别讲讲如何利用 JPA 实现排序和分页功能。

根据用户年龄的降序排列查询所有用户信息

在用户信息服务层中添加接口:

@Override
public List<User> findAllUser() {
   return userDao.findAll(new Sort(Sort.Direction.DESC,"age"));
    }

在 Controller 层中添加接口:

 @RequestMapping("/queryAllUser")
    public List<User> queryAllUser(){
        return userService.findAllUser();
    }

启动程序,利用 Postman 测试结果如下:

enter image description here

分页查询

为了让读者看清楚分页相关功能,我这边调用新增用户接口向用户表插入100条数据。

在用户持久层添加根据用户年龄降序排列,然后实现分页查询功能。

在用户服务层定义接口并在实现类中实现接口:

List<User> findUserByPageAndPageSize(int currentPage,int pageSize);

/**
 * 对用户年龄降序排列并实现分页查询
 * @param currentPage
 * @param pageSize
 * @return
 */
@Override
public List<User> findUserByPageAndPageSize(int currentPage, int pageSize) {
        Sort ageSort = new Sort(Sort.Direction.DESC,"age");

        Pageable pageable = PageRequest.of(currentPage,pageSize,ageSort);

        Page<User> page = userDao.findAll(pageable);

        if(page != null){
            return  page.getContent();
        }

        return null;
    }

JPA 框架除了能够根据方法名称映射为 SQL 语句操作数据库之外,还为开发者提供了直接利用 SQL 语句操作数据库的功能。

利用本地 SQL 语句对用户表数据进行修改、查询。

在用户持久层接口中,添加根据用户 id 修改用户信息,根据用户 id 查询用户信息接口,代码如下:

 @Modifying
 @Query(value = "update user set age = ?1 where id = ?2",nativeQuery = true)
 void updateUserInfoByUserId(Integer userId);

 @Query(value = "select * from user where id = ?1",nativeQuery = true)
 User queryUserInfoByUserId(Integer userId);

在用户服务层中,新增根据用户 id 修改用户年龄,根据用户 id 查询用户信息接口定义、实现。

接口定义如下:

void updateUserAgeByUserId(Integer age,Integer userId);

User getUserInfoByUserId(Integer userId);

接口实现,代码如下:

@Override
@Transactional
public void updateUserAgeByUserId(Integer age, Integer userId) {
    userDao.updateUserInfoByUserId(age,userId);
 }

@Override
public User getUserInfoByUserId(Integer userId) {
    return userDao.queryUserInfoByUserId(userId);
}

在控制层新增接口:

@RequestMapping("/updateUserAgeById")
public String updateUserAgeById(Integer age, Integer userId) {
    userService.updateUserAgeByUserId(age, userId);
    return "success";
}

@RequestMapping("/queryUserByUserId")
public User queryUserByUserId(Integer userId) {
    return userService.getUserInfoByUserId(userId);
}

启动程序,利用 Postman 分别对修改用户年龄、根据用户 id 查询用户信息接口进行测试:

enter image description here

enter image description here

通过上面的实例,相信各位读者已经注意到,我在 updateUserAgeById 方法上加了注解 @Transactional,原因是 JPA 在对数据库进行更新操作时默认需要开启事务,假如不开启事务,程序会报如下错误:

javax.persistence.TransactionRequiredException: Executing an update/delete query;

当开发者直接用本地 SQL 语句操作数据库时,需要在 @Query 注解属性中将 nativeQuery 设置为 true,当本地 SQL 语句对数据库表进行写操作时(包括 Update、Delete),还需要在方法上添加 @Modifying 注解。

需要注意的是,如果用本地 SQL 语句或者 JPQL 查询结果集并非 Entity 时,可以用 Object[] 数组代替,如查询用户信息表中年龄小于指定岁数且对应岁数的人数。

在持久层中添加接口:

@Query(value = "select age,count(*) from user where age < ?1 group by age",nativeQuery = true)
List<Object[]> getUserCount(Integer age);

在服务接口层定义接口,服务实现类中实现接口如下:

List<Map<String,Integer>> queryUserCountByAge(int age);

@Override
public List<Map<String,Object>> queryUserCountByAge(int age) {
    List<Object[]> list = userDao.getUserCount(age);

    if (!CollectionUtils.isEmpty(list)) {
        List<Map<String,Object>> mapList = new ArrayList<Map<String,Object>>();

        for(Object[] objects : list){
            Map<String,Object> map = new HashMap<String,Object>();
            map.put("age",objects[0]);
            map.put("userCount",objects[1]);
            mapList.add(map);
         }

         return mapList;
    }

    return null;
}

控制层接口定义如下:

@RequestMapping("/queryUserCountByAge")
public List<Map<String,Object>> updateUserAgeById(Integer age) {
    return userService.queryUserCountByAge(age);
}

启动程序,利用 Postman 测试如下:

enter image description here

整合非关系型数据库 Redis

为了演示 Spring Boot 整合 Redis 相关实例,首先在 CentOS 7 服务器上安装 Redis,过程如下。

1. 安装 GCC,执行命令:yum install -y gcc

2. 命令执行结束后,输入 gcc -v 命令检查 GCC 是否安装成功,如果执行命令后输出:gcc version 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC) 类似的信息说明 GCC 安装成功;

3. 执行命令:mkdir -p /usr/local/software/,再创建目录 /usr/local/software

4. 执行命令 cd /usr/local/software/ 进入 software 目录,然后执行命令 wget http://download.redis.io/releases/redis-4.0.6.tar.gz 下载 Redis 安装包;

5. 执行命令 tar -zxvf redis-4.0.6.tar.gz -C ../ 将 Redis 安装包解压到目录 /usr/local/中;

6. 执行命令 mv redis-4.0.6/ redis 修改目录名称;

7. 执行命令 cd redis,再执行命令 make MALLOC=libc 编译 Redis 安装包源文件;

8. 执行命令 cd src && make install 安装 Redis;

9. 新建目录 mkdir -p /usr/local/redis/bin /usr/local/redis/etc

10. 执行命令 cd /usr/local/redis/src,打开 src 目录,执行命令将对应的文件复制到上面新建的 bin、etc 目录下:

cp redis-server ../bin/
cp redis-benchmark ../bin/
cp redis-check-rdb ../bin/
cp redis-sentinel ../bin/
cp redis-cli ../bin/
cp ../redis.conf ../etc/

11. 执行命令 vim /etc/profile,修改环境变量文件,在文件中添加下面内容:

export REDIS_HOME=/usr/local/redis/
export PATH=${JAVA_HOME}/bin:${REDIS_HOME}/bin:$PATH

保存修改的内容后,执行 source /etc/profile 命令让修改环境变量文件生效。

12. 修改 /usr/local/redis/etc/redis.conf 配置文件,修改内容如下:

bind 192.168.1.120
bind 127.0.0.1

daemonize yes

dir /usr/local/redis/data/

appendonly yes
appendfsync always

以上配置含义以及 Redis 相关配置调优将在后面的 Redis 相关课程中进行详解。

13. 执行命令 redis-server /usr/local/redis/etc/redis.conf 启动 Redis 服务端;

14. 执行命令 ps -ef | grep 6379 检查 redis-server 是否已经正常启动。如输出如下信息说明启动成功:

enter image description here

接下来演示 Spring Boot 如何整合 Redis。

1. 在 pom 文件中添加依赖:

 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

2. 在配置文件中添加如下内容:

spring:
    redis:
    #设置数据库索引
    database: 0
    #Redis服务器地址
    host: 192.168.1.120
    #Redis服务器连接端口
    port: 6379
    #Redis服务器连接密码(默认为空)
    password: hcb13579
    #连接池最大连接数(使用负值表示没有限制)
    pool:
      max-active: 10
    #连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1
    #连接池中的最大空闲连接
      max-idle: 10
    #连接池中的最小空闲连接
      min-idle: 0
    #连接超时时间(毫秒)
    timeout:  60000

3. 添加操作 Redis 服务层接口:

/**
 * 利用Redis持久化用户信息
 */
public interface RedisUserService {

    /**
     * 根据用户uuid获取用户信息
     * @param uuid
     * @return
     */
    User getUserInfo(String uuid);

    /**
     * 将用户信息存入Redis
     * @param user
     */
    StringsaveUserInfo(User user);
}

4. 添加操作 Redis 服务实现类:

@Service("redisUserService")
public class RedisUserServiceImpl implements RedisUserService {
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public User getUserInfo(String uuid) {
        return (User) redisTemplate.opsForValue().get(uuid);
    }

    @Override
    public String saveUserInfo(User user) {
        redisTemplate.opsForValue().set(user.getUuid(),user);

        return user.getUuid();
    }
}

5. 在控制层中添加将用户信息存入 Redis、从 Redis 中查询用户信息的接口:

@RequestMapping("/saveUserInfoIntoRedis")
    public String saveUserInfoIntoRedis(String name,Integer age){
        User user = new User(name,age);
        user.setUuid(UUID.randomUUID().toString());

        return redisUserService.saveUserInfo(user);
    }

    @RequestMapping("/getUserInfoFromRedis")
    public User saveUserInfoIntoRedis(String uuid){
        String realKey = (String) redisTemplate.opsForValue().get(uuid);
        return redisUserService.getUserInfo(realKey);
    }

6. 启动程序,利用 Postman 测试如下:

enter image description here

enter image description here

实际工作中,Redis 除了用于实现缓存功能之外,其发布、订阅功能也比较重要。

下面为各位读者讲解如何使用这些功能。

首先和大家说一个 Redis 订阅、发布功能在实际工作中的应用场景。在未用 Redis 发布、订阅功能之前,我同事发送一个消息到消息中间件 RabbitMQ 中的某一个队列中,消息包括了能够代表本次请求的唯一 UUID,利用服务器端上另一个运行 Python 脚本的容器监听该队列,然后消费消息,接着将 Python 脚本处理后的结果放在 Redis 中。

Redis 中的 key 用来存放消息中间件的 UUID,value 存放 Python 处理的结果,而在微服务端循环地利用 UUID 去 Redis 中查询 Python 处理的结果。该种处理方式会在发送消息端一直循环,并同步查询结果,在我们不知道 Python 端处理请求需要多长时间时,该种处理方式就显得不太恰当了。

当类似的请求和用户之间有交互时,更是会严重影响用户体验。经过我们代码走查,最后建议将其改为 Redis 订阅、发布功能。这样处理的话,当服务调用方向消息中间件发送消息之后,会及时返回,等 Python 端处理完之后将结果放入 Redis 中,异步处理即可。

下面就为各位演示下 Redis 发布订阅功能如何实现。

1. 定义监听器:

public class RedisChannelListener implements MessageListener {
    @Override
    public void onMessage(Message message, byte[] bytes) {
        byte[] channelBytes = message.getChannel();

        byte[] bs = message.getBody();

        try {
            String content = new String(bs,"UTF-8");
            String channel = new String(channelBytes,"UTF-8");

            System.out.println("channel:" + channel + "---" + "message:" + content);
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

2. 设置监听器:

@Configuration
public class MessageListenerConfig {
    @Bean
    public MessageListenerAdapter listenerAdapter() {
        return new MessageListenerAdapter(new RedisChannelListener());
    }

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory redisConnectionFactory){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();

        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener(listenerAdapter(),new PatternTopic("redis.news.*"));

        return container;
    }
}

3. 添加控制器:

@RequestMapping("/redis")
@RestController
public class RedisController {
    @Autowired
    @Qualifier("publishRedisMessage")
    private PublishRedisMessage publishRedisMessage;

    @RequestMapping("/publishMessage")
    public String publishRedisMessage(String channel,String msg){
        return  publishRedisMessage.publishMessage(channel,msg);
    }
}

4. 启动程序利用 Postman 进行测试即可。

猜你喜欢

转载自blog.csdn.net/qq_41377914/article/details/85274025