项目档案

从JDBC到Hibernate

在Auction赢博体育程序的第一个版本中,我使用JDBC与数据库通信。我们使用Spring Boot提供的JdbcTemplate类来完成赢博体育的数据库交互,我们还为每个数据库交互编写了SQL。

在Auction赢博体育程序的下一个版本中,我们将使用Spring Data JPA和Hibernate的组合来处理赢博体育的数据库交互。Hibernate是对象关系映射系统(ORM)的一个例子。Hibernate自动化了将数据库表中的数据转换为Java对象的过程。Jakarta Persistence Architecture (JPA)是一组用于处理Hibernate等ORM系统的软件标准。Spring Data是一个Spring项目,旨在使JPA和ORM系统的使用更简单、更直接。

在Auction赢博体育程序的下一个版本中,我将使用Hibernate,但是我还没有充分利用Spring Data提供的强大功能。我将在这个版本之后的下一个版本中演示如何更全面地使用Spring Data。

这些笔记中的材料是基于我们课本第六章的材料。为了更全面地了解Hibernate的功能,您应该在回顾这些注释后阅读该章。

开始

使用Hibernate的第一步是设置一个新项目。在初始项目配置中,我们将进行一个重要的更改。我们将用Spring Data JPA依赖项代替JDBC API项目依赖项。

在初始项目配置中,我们仍然将使用相同的MySQL数据库。赢博体育程序中的条目。设置数据库连接的属性文件将与我们的第一个项目保持相同:

spring.datasource。url = jdbc: mysql: / / localhost: 3306 /拍卖spring.datasource。用户名=学生spring.datasource.password = Cmsc250 !spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver

Hibernate以两种模式运行。我们可以使用Hibernate从我们定义的一组Java类中自动生成一组数据库表,或者我们也可以设置一组Java类来对数据库中已经存在的表进行建模。在下面的示例中,我们将采用后一种方法。

实体类

使用Hibernate的第一步是为数据库中的每个表设置一个Entity类。当Hibernate从数据库表中提取数据时,它会将数据转换为与表对应的Java Entity类。

这里有一个典型的例子。数据库中比较简单的表之一是shipping表,它的结构是这样的:

下面是这个表对应的Entity类:

@实体公共类Shipping {@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer shippingid;@ManyToOne @JoinColumn(name="user") private user user;private String displayname;私有字符串addressone;私有字符串addresst2;private String city;私有字符串状态;private String zip;public Shipping(){} //未显示getter和setter}

以下是关于这个类需要注意的一些重要事项:

下面是第二个稍微复杂一点的例子。下面是Hibernate Entity类,用于匹配数据库中的users表。

@Entity @Table(name="users“)公共类User {@Id @GeneratedValue(strategy = GenerationType.UUID) @Column(columnDefinition =” VARCHAR(45)") @JdbcTypeCode(SqlTypes.VARCHAR) private UUID userid;私有字符串名称;private String密码;@OneToOne(mappedBy="user") private Profile Profile;@OneToMany(mappedBy="user") List<Shipping> Shipping;@OneToMany(mappedBy="seller") List<Auction> auctions;public User(){} //省略Getter和setter方法}

以下是关于这个例子需要注意的一些额外事项:

使用EntityManager类

为了让实体对象进出数据库,我们将使用EntityManager对象。该对象将作为@Repository类中的成员变量出现。

下面是一个简单的示例来展示如何设置这一切。下面是AuctionDAO类的一部分:

@Repository公共类AuctionDAO {@Autowired EntityManager em;@Transactional public String save(AuctionDTO auction) {User User = em.find(User.class,UUID.fromString(auction. getseller ())));if(user == null) return "Bad Id";拍卖newwauction =新拍卖(拍卖);newAuction.setSeller(用户);em.persist (newAuction);for(String t: auction.getTags()) {Tag newTag = newTag ();newTag.setAuction (newAuction);newTag.setTag (t);em.persist (newTag);}返回newAuction.getAuctionid().toString();}}

以下是关于这个例子需要注意的一些事情:

使用自定义JPQL查询

Hibernate为我们自动化了大量的数据库操作。例如,如果我们知道一个用户的UUID,并希望获得该用户发布的赢博体育Auction对象的列表,我们只需这样做

User User = em.find(User.class,userid);List<Auction> auctions = user.getAuctions();

在一些相对罕见的情况下,我们将不得不为Hibernate提供一些额外的帮助来查找内容。一个典型的例子是查找当前赢博体育“现场”拍卖的列表。下面是AuctionDAO的方法:

@Transactional public List<Auction> findActive() {return em.createQuery("select a from Auction a where open <=:now and closing >=:now",Auction.class) .setParameter("now", LocalDate.now()).getResultList();}

这段代码使用JPQL查询语言运行一个自定义查询。JPQL本质上是SQL的面向对象变体。就像SQL中的预处理语句一样,JPQL查询可以具有占位符,这些占位符将被随后对setParameter()的调用所替换。教材的第六章提供了关于JPQL如何工作的更多信息,并给出了许多示例。

DTO类

Entity类的一个不幸问题是,它们只适合从数据库中获取数据。我们的赢博体育程序还将以REST控制器为特色,这些控制器将向客户端传递对象和从客户端传递对象。有一种倾向是设置控制器方法来简单地返回实体类:例如,我们的REST方法之一是GET /auctions,它应该返回一个“实时”拍卖列表。我们可以这样写这个方法:

@GetMapping public List<Auction> getActiveAuctions() {return dao.findActive();}

这几乎可以工作:Spring Boot可以将Auction对象列表转换为JSON并将其发送给客户端。不幸的是,客户机将获得的JSON将有一个非常大且明显的问题。要了解这个问题是什么,下面是如何定义Auction Entity类:

@Entity @Table(name="auctions“)公共类Auction {@Id @GeneratedValue(strategy = GenerationType.UUID) @Column(columnDefinition =” VARCHAR(45)") @JdbcTypeCode(SqlTypes.VARCHAR) private UUID auctionid;@ManyToOne @JoinColumn(name="seller") private User seller;私有字符串项;私有字符串描述;私有字符串imageurl;私人储备;private LocalDate打开;private LocalDate关闭;私有布尔值完成;@OneToMany(mappedBy="auction") List<Tag> tags;@OneToMany(mappedBy="auction") List<Bid>出价;public Auction() {} // Getters和setters的剩余}

这里的问题是拍卖实体包含了对其他类的引用。当Auction对象转换为JSON时,嵌入的对象和列表将嵌入到Auction的JSON代码中。这反过来会导致大麻烦:例如,这里的嵌入式User对象还包含该User拥有的赢博体育拍卖的列表。每个拍卖依次包含对一个用户的引用,该用户包含更多的拍卖,依此类推。

这个问题的解决方案是永远不要将Entity类作为REST请求的响应发送给客户端。相反,我们将把每个实体类与一个称为数据传输对象(DTO)的伙伴类耦合起来。DTO类被设计成以一种简单的方式与JSON进行转换。例如,下面是AuctionDTO类的代码:

公共类AuctionDTO{私有字符串auctionid;私人字符串卖家;私有字符串项;私有字符串描述;私有字符串imageurl;私人储备;private String打开;private String关闭;私有布尔值完成;private List<String> tags;public AuctionDTO(){这个。tags = new ArrayList<String>();} public AuctionDTO(拍卖核心){auctionid = core. getauctionid ().toString();卖方= core.getSeller().getUserid().toString();item = core.getItem();description = core.getDescription();imageurl = core.getImageurl();reserve = core. gereserve ();open = core.getOpens().toString();close = core. getclose ().toString();completed = core.getCompleted();tags = new ArrayList<String>();tagList = core.getTags();for(Tag t: tagList) {Tag .add(t. gettag ());}} //省略getter和setter}

DTO类的关键特性之一是自定义构造函数,它可以从相应的Entity类初始化DTO。

下面是返回活动拍卖列表的REST控制器方法的实际代码:

@GetMapping public ResponseEntity<List<AuctionDTO>> findActiveAuctions() {List<Auction> auctions = dao.findActive();List<AuctionDTO> results = new ArrayList<AuctionDTO>();for(拍卖a:拍卖){结果。添加(新AuctionDTO (a));}返回ResponseEntity.ok().body(results);}

该方法返回AuctionDTO对象列表,而不是更有问题的Auction对象。