springboot+JPA+PostgreSQL入门

1.前言

有人说,程序员多掌握一种数据库,就多一门手艺。其实在我看来,多学一种数据库可以让知识更好的碰撞,你将看到一番不一样的视野。今天就开启我们的PostgreSQL之旅。

2.pom文件中的核心依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>test</scope>
        </dependency>

简单说明:(1)上面的依赖中的lombok是我最近常使用的jar,它真得帮我们省下很多getter和setter代码,多用几次,你也会和我一样喜欢上它的;(2)还有就是hsqldb是一个内嵌的内存数据库,用来测试再好不过了。

3.配置文件

(1) application.yaml

(2) application-dev.yaml

spring:
  profiles:
    active: dev
spring:
  application:
    name: postgresql-demo
  datasource:
    url: jdbc:postgresql://localhost:5432/test
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver
  jpa:
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
        dialect: org.hibernate.dialect.PostgreSQLDialect
        hbm2ddl:
          auto: update

(3) application-test.yaml

spring:
  application:
    name: postgresql-demo
  jpa:
    database: hsql

4.项目目录结构

5.几个核心类 java代码

(1) GeneralResource.java

@MappedSuperclass
@NoArgsConstructor
public abstract class GeneralResource {
    @Id
    @GenericGenerator(
            name = "uuid",
            strategy = "uuid2"
    )
    @GeneratedValue(
            generator = "uuid"
    )
    @Column(
            length = 100
    )
    @Getter
    @Setter
    private String id;
}

(2) Resource.java

@MappedSuperclass
@NoArgsConstructor
public abstract class Resource extends GeneralResource{
    @Column(
            length = 60,
            nullable = false
    )
    @Getter
    @Setter
    private String creatorId;

    @Getter
    @Setter
    private Long creationTime;
}

(3) User.java

@Entity
@Table(name = "t_user")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@NoArgsConstructor
@AllArgsConstructor
public class User extends Resource {
    @Getter
    @Setter
    private String name;
}

(4) UserDTOHelper.java

@Component
public class UserDTOHelper {
    public UserDTO toDto(User user) {
        return new UserDTO()
                .id(user.getId())
                .name(user.getName())
                .creatorId(user.getCreatorId());
    }

    public List<UserDTO> toDtos(List<User> users) {
        if (CollectionUtils.isEmpty(users)) {
            return Collections.emptyList();
        }
        return users
                .parallelStream()
                .map(this::toDto)
                .collect(toList());
    }

    public User toModel(@NotNull UserDTO dto) {
        User user = new User();
        user.setId(dto.getId());
        user.setName(dto.getName());
        user.setCreatorId(dto.getCreatorId());
        return user;
    }
}

(5) UserRepository.java

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, String> {
}

(6) UserService.java

public interface UserService {

    User createUser(@NotNull User user);


    Optional<User> findById(String id);

}

(6) UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
    private final UserRepository repository;

    @Autowired
    public UserServiceImpl(UserRepository repository) {
        this.repository = repository;
    }

    @Override
    public User createUser(@NotNull User user) {
        return repository.save(user);
    }

    @Override
    public Optional<User> findById(String id) {
        return repository.findById(id);
    }

}

(7) UserDTO.java

public class UserDTO implements Serializable {
    @Getter
    @Setter
    private String id;

    @Getter
    @Setter
    private String name;

    @Getter
    @Setter
    private String creatorId;

    public UserDTO id(String id) {
        this.id = id;
        return this;
    }

    public UserDTO name(String name) {
        this.name = name;
        return this;
    }

    public UserDTO creatorId(String creatorId) {
        this.creatorId = creatorId;
        return this;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserDTO that = (UserDTO) o;

        return Objects.equals(this.creatorId, that.creatorId) &&
                Objects.equals(this.id, that.id) &&
                Objects.equals(this.name, that.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(creatorId, id, name);
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", this.getClass().getSimpleName() + "[", "]")
                .add("creatorId = " + creatorId)
                .add("id = " + id)
                .add("name = " + name)
                .toString();
    }
}

(8) UserController.java

@RestController
public class UserController {
    private final UserService service;

    @Autowired
    public UserController(UserService service) {
        this.service = service;
    }


    public ResponseEntity<User> queryById(String id){
        return new ResponseEntity<>(service.findById(id).orElse(new User("Unknown")), HttpStatus.OK);
    }
}

(9) UserServiceTest.java

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserDTOHelper helper;

    @Test
    public void testSave() {
        UserDTO dto = buildDto();
        User user = helper.toModel(dto);
        User savedUser = userService.createUser(user);
        Optional<User> optionalUser = userService.findById(savedUser.getId());

        assertTrue(optionalUser.isPresent());
        assertEquals(dto.getName(), optionalUser.get().getName());
    }

    private UserDTO buildDto() {
        return new UserDTO()
                .name("Test")
                .creatorId(UUID.randomUUID().toString());
    }
}

6.后记

(1)做一个入门其实不是很难,但是从头开始学习一个自己从未接触到的框架也好,数据库也好,或者其他的技术也好,都不是一件容易的事。但是错误总是接踵而来很容易打击到一颗脆弱的心灵,还好再坚持一下就好了。

比如上面的代码例子其实不是很难,那是站在代码的角度,但是其中还是有不少的坑的。我下面我将演示上面代码中如果少了点什么,或者写错点什么的效果截图。


看到日志中的错误就是:
Caused by: java.sql.SQLFeatureNotSupportedException: 这个 org.postgresql.jdbc.PgConnection.createClob() 方法尚未被实作 。

尽管测试是通过了,但是有强迫症的你会没有疑问吗?为什么会出现这样的问题?!答案是我注释掉了一行配置代码

(2)还有就是一个很小的细节问题,现在Java开发主流是使用注解,但是有时我们却忘了加上去,比如我昨天就忘了在User上面加上@Table注解,当然和数据库打交道的时候,Java中的映射可能会和数据中的关键字有冲突,可以推荐表名使用一个前缀,比如t_。还有就是在使用Hibernate中的继承机制的时候,如果我们知道表的结构就容易理解这个注解@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

(3)最后还有两个小点,一个就是设计一些基础的Base类,你的代码结构将更加层次化,而且这些思想的产物是可以复用的。还有一个就是Java8汇总引入的Stream Api让你的代码更加流畅,简洁,你值得拥有。