如果项目持久层使用Spring Data JPA且某些数据表中含有复合主键(联合主键),使用Spring Data Rest生成的接口如何访问这些数据表中的某个主键对应的数据呢?
假设数据库有2个有复合主键的数据表film_actor和film_category,对应以下4个类。
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.sql.Timestamp;
@Entity
@Table(name = "film_actor")
public class FilmActor implements Serializable {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name = "actorId", column = @Column(name = "actor_id", nullable = false)),
@AttributeOverride(name = "filmId", column = @Column(name = "film_id", nullable = false))})
private FilmActorId id;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_update", nullable = false, length = 19)
private Date lastUpdate;
//constructor、getter/setter
}
@Embeddable
public class FilmActorId implements java.io.Serializable {
@Column(name = "actor_id", nullable = false)
private short actorId;
@Column(name = "film_id", nullable = false)
private short filmId;
//constructor、getter/setter、equals、hashCode
}
@Entity
@Table(name = "film_category")
@IdClass(FilmCategoryPK.class)
public class FilmCategory {
@Id
@Column(name = "film_id", nullable = false)
private short filmId;
@Id
@Column(name = "category_id", nullable = false)
private byte categoryId;
@Basic
@Column(name = "last_update", nullable = false)
private Timestamp lastUpdate;
//constructor、getter/setter、equals、hashCode
}
public class FilmCategoryPK implements Serializable {
@Column(name = "film_id", nullable = false)
@Id
private short filmId;
@Column(name = "category_id", nullable = false)
@Id
private byte categoryId;
//constructor、getter/setter、equals、hashCode
}
以上代码代表了ORM的两种方式。
那如何通过Spring Data Rest生成的接口获取film_actor表中ID都是1的记录呢?
那就涉及如何将请求URL转化为实体类ID了。需要添加如下配置
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Arrays;
@Component
public class IdConverter implements BackendIdConverter {
private ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public Serializable fromRequestId(String s, Class> clazz) {
System.out.println(String.format("fromRequestId(%s,%s)", s, clazz));
threadLocal.set(s);
String[] ss = s.split(",");
long p1 = Long.parseLong(ss[0]);
long p2 = Long.parseLong(ss[1]);
if (clazz == FilmActor.class)
return new FilmActorId(((short) p1), ((short) p2));
if (clazz == FilmCategory.class)
return new FilmCategoryPK(((short) p1), ((byte) p2));
return null;
}
@Override
public String toRequestId(Serializable serializable, Class> clazz) {
System.out.printf("toRequestId(%s,%s)\n", serializable, clazz);
System.out.println(serializable.getClass());
return threadLocal.get();
}
@Override
public boolean supports(Class> clazz) {
System.out.printf("supports(%s)\n", clazz);
return Arrays.asList(FilmActor.class, FilmCategory.class).contains(clazz);
}
}
现在访问 http://localhost:8080/FilmActor/1,1 就能获取film_actor表
中actor_id和film_id都是1的记录了。
Spring data Rest生成的接口默认不输出ID,如果需要输出ID,可以配置一个Bean。如下是FilmActor、 FilmCategory都暴露ID的配置。
import org.springframework.context.annotation.Bean;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
@Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(FilmActor.class, FilmCategory.class);
}
};
}
附:
对Spring Data Rest的评价:
优点:用极少的代码就能实现数据库常用CRUD操作向RESTful API的转化
缺点:
1、springfox swagger还不能探测到生成的接口
2、与Spring Data JPA的结合还有很多问题
3、生成的接口难以应对复杂的业务场景
4、性能问题和安全问题
……