系統架構
使用 Spring orm 開發 JPA 數據持久層,用 Spring data jpa 簡化 DAO實現,真的不用寫 SQL 了
先導包:pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cheery.recruitment</groupId>
<artifactId>Recruitment-Webservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Recruitment-Webservice</name>
<description>Recruitment-Webservice</description>
<dependencies>
<!-- spring-context: spring 框架核心jars -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring-orm: 包含 orm 開發的jars,事務也會有 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<!-- hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.12.Final</version>
</dependency>
<!-- mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!-- commons-dbcp2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
</project>
架構一覽:
Spring 核心,依賴注入,並用容器管理 JPA EntityManager …
JPA , 持久化API ,實現選擇 Hibernate
MySQL ,數據庫
DBCP, 連接池
Spring data jpa, 簡化 DAO 實現
完整示例一個
招聘信息,公司發佈招聘信息,查詢相關信息。
1、 ORM 設計
很簡單,兩個 Entity ,在 domain 包下面:
Company: 公司類,name 唯一、非空 ,city非空(方便測試)
package com.cheery.recruitment.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="company")
public class Company {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column(nullable=false,unique=true)
private String name; // 名稱:阿里巴巴
@Column(nullable=false)
private String city; // 城市: 杭州
private String address; // 公司地址
private String website;// 公司網站
private String industry;// 行業
private String description;// 簡介
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
Recruitment :招聘信息類,外鍵關聯 company_id(one-to-one), job+company 構成唯一性約束,城市和工資非空(方便測試)
package com.cheery.recruitment.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "recruitment", uniqueConstraints = { @UniqueConstraint(columnNames = { "job", "company_id" }) })
public class Recruitment {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String job; // 崗位:Java高級工程師
@OneToOne
@JoinColumn(name = "company_id", nullable = false)
private Company company; // 公司:阿里巴巴
private String city;// 工作地點:杭州
private int salary;// 薪酬,月薪:20000
private String education;// 學歷要求:小學
private short experience;// 工作經驗:5, 0(應屆生)
private Date publishTime;// 發佈時間:2017-12-25
private Date deadline;// 有效期至:201s7-12-16, null(長期有效)
private String industry; // 行業:IT
private String responsibility; // 崗位職責:1.2.3...
private String requirement;// 崗位要求:1.2.3...
private String welfare;// 福利:五險一金,100個月年終獎...
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
public short getExperience() {
return experience;
}
public void setExperience(short experience) {
this.experience = experience;
}
public Date getPublishTime() {
return publishTime;
}
public void setPublishTime(Date publishTime) {
this.publishTime = publishTime;
}
public Date getDeadline() {
return deadline;
}
public void setDeadline(Date deadline) {
this.deadline = deadline;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getResponsibility() {
return responsibility;
}
public void setResponsibility(String responsibility) {
this.responsibility = responsibility;
}
public String getRequirement() {
return requirement;
}
public void setRequirement(String requirement) {
this.requirement = requirement;
}
public String getWelfare() {
return welfare;
}
public void setWelfare(String welfare) {
this.welfare = welfare;
}
}
2. DAO 實現
使用 spring data jpa 實現 dao 不要太簡單
核心原理:繼承 JpaRepository 的接口後,框架會自動生成對應的實現類,並實現了一系列的方法,CRUD 根本沒問題。要啓用這個功能,必須進行配置,註解實現是這樣的:
@EnableJpaRepositories(basePackages="com.cheery.recruitment.repository")
package com.cheery.recruitment.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.cheery.recruitment.domain.Company;
public interface CompanyRepo extends JpaRepository<Company, Integer>{
//注意哦,這個方法 spring data jpa 也會動態生成實現的哦!
public List<Company> readByCity(String city);
public Company findCompanyByName(String name);
}
package com.cheery.recruitment.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.cheery.recruitment.domain.Recruitment;
public interface RecruitmentRepo extends JpaRepository<Recruitment, Long> {
// 指定城市工資超過多少的指定工作, 這個方法也會動態實現的哦!
public List<Recruitment> readByJobLikeAndCityAndSalaryGreaterThanEqual(String job,String city,int salary);
}
說明:
- 上面兩個接口都實現了 JpaRepository , spring data jpa 會在 Spring application context 創建之後創建實現類,默認情況下,實現類名=接口類名+Impl 。也就是說,會創建兩個實現類, CompanyRepoImpl 和 RecruitmentRepoImpl 。
- JpaRepository 接口及其父接口定義的方法,實現類都會自動實現,同時,Spring data jpa 還支持定製的查詢方法,如 “readByJobLikeAndCityAndSalaryGreaterThanEqual”,這個方法符合,這些方法稱爲 “Repository methods”。
他們的格式是:查詢動詞+查詢對象(可省略)+By+查詢條件
這裏 read 是查詢動詞,與 get和find作用相同,還有一個查詢動詞 count 。這個方法省略了對象聲明 “Recruitments”,所以 readRecruitments… 效果一樣。
“JobLikeAndCityAndSalaryGreaterThanEqual”是查詢條件,多個條件用 And 或 Or 組合,條件可以是 SQL支持的多種之一,這裏用到的是,Like (job 模糊查詢), =(city =‘杭州’),>=(salary >=30000)- 如果 Repository methods ,還不能滿足需求,可以再定義一個DAO 接口,如 CompanyCustRepo , 在其中聲明定製的方法。然後讓實現類實現該接口,如 CompanyRepoImpl , 你只要在該類中實現 CompanyCustRepo 的方法,CompanyRepo 中的方法自動合併到這個類實現中,前提是,這個類名只能是CompanyRepo +Impl 不能隨便改了。
JPA 配置
由於該 demo 用 java app 測試,所以配置相對簡單,使用java 註解的配置方式
RepoConfig:Dao 配置
package com.cheery.recruitment.configure;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan(basePackages="com.cheery.recruitment.repository")
@EnableJpaRepositories(basePackages="com.cheery.recruitment.repository")
@EnableTransactionManagement
public class RepoConfig {
@Bean(destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///recruitment");
dataSource.setUsername("XXX");
dataSource.setPassword("XXX");
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan("com.cheery.recruitment.domain");
factoryBean.setJpaPropertyMap(jpaProperties());
return factoryBean;
}
@Bean
@Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
private Map<String, ?> jpaProperties() {
Map<String, String> jpaPropertiesMap = new HashMap<String, String>();
jpaPropertiesMap.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
jpaPropertiesMap.put("hibernate.hbm2ddl.auto", "update");
return jpaPropertiesMap;
}
}
@Configuration : 聲明這是配置類
@ComponentScan(basePackages=”com.cheery.recruitment.repository”) : 啓用基於註解的 bean 定義
@EnableJpaRepositories(basePackages=”com.cheery.recruitment.repository”) :啓用 Spring data jpa JpaRepositories 功能
@EnableTransactionManagement:啓用事務管理
ServiceConfig : Service 層配置
package com.cheery.recruitment.configure;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan(basePackages="com.cheery.recruitment.service")
@EnableTransactionManagement
public class ServiceConfig {
}
4.Service 層
package com.cheery.recruitment.service;
import java.util.List;
import com.cheery.recruitment.domain.Company;
public interface CompanyService {
public boolean saveCompany(Company company);
public Company getCompanyByName(String name);
public List<Company> findAllCompanies();
public List<Company> findCompaniesByCity(String city);
}
package com.cheery.recruitment.service;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cheery.recruitment.domain.Company;
import com.cheery.recruitment.repository.CompanyRepo;
@Service
@Transactional
public class CompanyServiceImpl implements CompanyService {
@Autowired
private CompanyRepo companyDao;
@Override
public boolean saveCompany(Company company) {
if (companyDao.save(company) == null) {
return false;
} else {
return true;
}
}
@Override
public List<Company> findAllCompanies() {
return companyDao.findAll();
}
@Override
public List<Company> findCompaniesByCity(String city) {
return companyDao.readByCity(city);
}
@Override
public Company getCompanyByName(String name) {
return companyDao.findCompanyByName(name);
}
}
package com.cheery.recruitment.service;
import java.util.List;
import com.cheery.recruitment.domain.Recruitment;
public interface RecruitmentService {
public boolean saveRecruitment(Recruitment recruitment);
public List<Recruitment> findAllRecruitments();
public List<Recruitment> findRecruitmentsByCriteria(String job,String city,int salary);
}
package com.cheery.recruitment.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cheery.recruitment.domain.Recruitment;
import com.cheery.recruitment.repository.RecruitmentRepo;
@Service
public class RecruitmentServiceImpl implements RecruitmentService {
@Autowired
private RecruitmentRepo recruitmentDao;
@Override
public boolean saveRecruitment(Recruitment recruitment) {
if (recruitmentDao.save(recruitment) == null) {
return false;
} else {
return true;
}
}
@Override
public List<Recruitment> findAllRecruitments() {
return this.recruitmentDao.findAll();
}
@Override
public List<Recruitment> findRecruitmentsByCriteria(String job, String city, int salary) {
String jobCriteria="%"+job+"%";
return this.recruitmentDao.readByJobLikeAndCityAndSalaryGreaterThanEqual(jobCriteria, city, salary);
}
}
5. 應用層
package com.cheery.recruitment;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.cheery.recruitment.configure.RepoConfig;
import com.cheery.recruitment.configure.ServiceConfig;
import com.cheery.recruitment.domain.Company;
import com.cheery.recruitment.domain.Recruitment;
import com.cheery.recruitment.service.CompanyService;
import com.cheery.recruitment.service.RecruitmentService;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(RepoConfig.class,
ServiceConfig.class);
CompanyService companyService = (CompanyService) applicationContext.getBean("companyServiceImpl");
RecruitmentService recruitService=(RecruitmentService) applicationContext.getBean("recruitmentServiceImpl");
//加幾個公司試試
initCompanyInfo(companyService);
System.out.println("系統當前的公司列表:");
printCompanies(companyService.findAllCompanies());
System.out.println("系統當前的杭州公司列表:");
printCompanies(companyService.findCompaniesByCity("杭州"));
//加幾條招聘信息試試
initReruitmentInfo(companyService, recruitService);
//查找杭州工資超過30000的Java相關工作
printRecruitments(recruitService.findRecruitmentsByCriteria("Java", "杭州", 30000));
}
private static void initCompanyInfo(CompanyService companyService) {
// 新增三個公司
Company alibaba = new Company();
alibaba.setName("阿里巴巴");
alibaba.setCity("杭州");
Company neteasy = new Company();
neteasy.setCity("杭州");
neteasy.setName("網易");
Company tencent = new Company();
tencent.setCity("深圳");
tencent.setName("騰訊");
companyService.saveCompany(alibaba);
companyService.saveCompany(neteasy);
companyService.saveCompany(tencent);
}
private static void initReruitmentInfo(CompanyService companyService, RecruitmentService recruitService) {
// 阿里巴巴發佈兩條招聘信息
Recruitment ali_recruit1 = new Recruitment();
ali_recruit1.setJob("Java初級工程師");
ali_recruit1.setCity("杭州");
ali_recruit1.setCompany(companyService.getCompanyByName("阿里巴巴"));
ali_recruit1.setSalary(20000);
recruitService.saveRecruitment(ali_recruit1);
Recruitment ali_recruit2 = new Recruitment();
ali_recruit2.setJob("Java高級工程師");
ali_recruit2.setCity("杭州");
ali_recruit2.setCompany(companyService.getCompanyByName("阿里巴巴"));
ali_recruit2.setSalary(40000);
recruitService.saveRecruitment(ali_recruit2);
// 網易發佈兩條招聘信息
Recruitment neteasy_recruit1 = new Recruitment();
neteasy_recruit1.setJob("Java高級工程師");
neteasy_recruit1.setCity("杭州");
neteasy_recruit1.setCompany(companyService.getCompanyByName("網易"));
neteasy_recruit1.setSalary(30000);
recruitService.saveRecruitment(neteasy_recruit1);
Recruitment neteasy_recruit2 = new Recruitment();
neteasy_recruit2.setJob("Python高級工程師");
neteasy_recruit2.setCity("杭州");
neteasy_recruit2.setCompany(companyService.getCompanyByName("網易"));
neteasy_recruit2.setSalary(35000);
recruitService.saveRecruitment(neteasy_recruit2);
// 騰訊發佈兩條招聘信息
Recruitment tencent_recruit1 = new Recruitment();
tencent_recruit1.setJob("Java開發工程師");
tencent_recruit1.setCity("深圳");
tencent_recruit1.setCompany(companyService.getCompanyByName("騰訊"));
tencent_recruit1.setSalary(20000);
recruitService.saveRecruitment(tencent_recruit1);
Recruitment tencent_recruit2 = new Recruitment();
tencent_recruit2.setJob("Java高級工程師");
tencent_recruit2.setCity("深圳");
tencent_recruit2.setCompany(companyService.getCompanyByName("騰訊"));
tencent_recruit2.setSalary(40000);
recruitService.saveRecruitment(tencent_recruit2);
}
private static void printCompanies(List<Company> companies) {
for (Company c : companies) {
System.out.println(c.getName());
}
}
private static void printRecruitments(List<Recruitment> recruitments) {
for (Recruitment r : recruitments) {
String name=r.getJob()+"("+r.getCompany().getName()+")"; //招聘信息名稱:job(companyName)
String salary=r.getSalary()+"元/月";
System.out.println(name+"--"+r.getCity()+"--"+salary);
}
}
}
總結
在以業務爲中心的應用中,使用 orm 框架還是很有優勢的,而使用 orm框架,則使用JPA設計不依賴與特定提供商的代碼很有必要,這樣能輕易在供應商之間切換。就 JPA 實現來說,Hibernate 是不錯的選擇,也是 Spring orm 默認的選擇。
使用 spring data jpa 可以輕鬆實現 dao ,再也不用什麼泛型啊,反射啊,功能很強大!