整合 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 表中插入三条数据:

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

在 Postman 输入请求地址:

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

对查询接口进行测试:

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

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

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

没错,看了上面官网给出的 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 测试结果如下:

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

分页查询

为了让读者看清楚分页相关功能,我这边调用新增用户接口向用户表插入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 查询用户信息接口进行测试:

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

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

通过上面的实例,相信各位读者已经注意到,我在 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 测试如下:

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

整合非关系型数据库 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 是否已经正常启动。如输出如下信息说明启动成功:

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

接下来演示 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 测试如下:

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

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

实际工作中,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 进行测试即可。

相关文章:

  • 2021-08-27
  • 2021-04-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-09
  • 2021-05-27
  • 2021-11-10
猜你喜欢
  • 2021-10-10
  • 2021-11-06
  • 2021-10-31
  • 2022-12-23
  • 2021-11-16
  • 2021-06-22
  • 2021-08-15
相关资源
相似解决方案