第一個springboot+jpa+oracle+maven專案
大概花了一週的時間完成了第一個springboot專案的搭建,使用的工具是Eclipse,遇到不少問題,這裡強烈推薦《Spring實戰》這本書,有些問題在網上搜了半天但是都不能解決問題,後來都是在這本書中找到解決方法的。 專案結構:
pom.xml檔案
<?xml version="1.0" encoding="UTF-8"?> <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.0http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com</groupId> <artifactId>ypjgnew</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>ypjgnew</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> <version>2.0.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <!-- Spring Boot log4j依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.4.RELEASE</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.9</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> </plugin> </plugins> </build> </project>
主要配置檔案:application.properties
#主資料來源 spring.datasource.driver=oracle.jdbc.driver.OracleDriver spring.datasource.url=jdbc:oracle:thin:@ spring.datasource.username= spring.datasource.password= #輔資料來源 spring.datasource.auth.driver=oracle.jdbc.driver.OracleDriver spring.datasource.auth.url=jdbc:oracle:thin:@ spring.datasource.auth.username= spring.datasource.auth.password= #Spring Data JPA spring.jpa.database=oracle # Naming strategy spring.jpa.hibernate.naming-strategy = org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl # stripped before adding them to the entity manager) spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle9Dialect #熱部署 spring.devtools.restart.enabled: true #根路徑 server.servlet.context-path=/ypjg spring.mvc.view.prefix=classpath:/resources/templates/ spring.mvc.view.suffix=.html spring.mvc.static-path-pattern=/static/** spring.mvc.favicon.enabled=false #實體類的掃描路徑,因為有兩個資料來源 packages.to.scan.auth=com.login.model packages.to.scan=com.ypjg.model
配置類,這次用了java類的方式來建立bean,沒有采用xml的方式。因為有兩個資料來源,所以把一些基礎的配置單獨寫了一個類,另外兩個配置對應資料庫的類繼承這個基礎類。 基礎配置類:BaseConfiguration
@Configuration @EnableTransactionManagement @PropertySource("classpath:application.properties") public class BaseConfiguration { @Autowired protected Environment env; /** * 把HibernateExceptions轉換成DataAccessExceptions */ @Bean public HibernateExceptionTranslator hibernateExceptionTranslator() { return new HibernateExceptionTranslator(); } /** * hibernate配置 * @return */ protected Properties hibernateProperties() { Properties properties = new Properties(); // 方言 properties.put("spring.jpa.properties.hibernate.dialect", env.getRequiredProperty("spring.jpa.properties.hibernate.dialect")); // 自動生成表 //properties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("spring.jpa.hibernate.ddl-auto")); // 名字策略 properties.put("spring.jpa.hibernate.naming.physical-strategy", env.getRequiredProperty("spring.jpa.hibernate.naming-strategy")); return properties; } }
配置主資料來源的類:PrimaryConfig
@Configuration
@EnableJpaRepositories(basePackages = {"com.ypjg.daos"}, entityManagerFactoryRef = "entityManagerFactoryPrimary",
transactionManagerRef = "transactionManagerPrimary")
public class PrimaryConfig extends BaseConfiguration {
/**
* 1.配置主資料來源
*
* @return DataSource
*/
@Bean(name = "primaryDataSource")
@Primary
@Qualifier("primaryDataSource")
public DataSource primaryDataSource() {
DruidDataSource source = new DruidDataSource();
source.setDriverClassName(env.getRequiredProperty("spring.datasource.driver"));
source.setUrl(env.getRequiredProperty("spring.datasource.url"));
source.setUsername(env.getRequiredProperty("spring.datasource.username"));
source.setPassword(env.getRequiredProperty("spring.datasource.password"));
try {
source.addFilters("log4j");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return source;
}
/**
* 2.配置EntityManagerFactory
*
* @return
*/
@Primary
@Bean(name = "entityManagerFactoryPrimary")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
// 配置資料來源
factory.setDataSource(primaryDataSource());
// VendorAdapter
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
// entity包掃描路徑
factory.setPackagesToScan(env.getRequiredProperty("packages.to.scan"));
// jpa屬性
factory.setJpaProperties(hibernateProperties());
factory.afterPropertiesSet();
return factory;
}
/**
* 3.事務管理器配置
*
* @return
*/
@Bean(name="transactionManagerPrimary")
@Primary
public PlatformTransactionManager transactionManagerPrimary() {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(entityManagerFactoryPrimary().getObject());
return manager;
}
}
配置輔資料來源的類:SecondConfig
@Configuration
@EnableJpaRepositories(basePackages = {"com.login.daos"}, entityManagerFactoryRef = "entityManagerFactorySecondary",
transactionManagerRef = "transactionManagerSecondary")
public class SecondConfig extends BaseConfiguration {
@Bean(name = "secondaryDataSource")
@Qualifier("secondaryDataSource")
public DataSource secondaryDataSource() {
DruidDataSource source = new DruidDataSource();
source.setDriverClassName(env.getRequiredProperty("spring.datasource.auth.driver"));
source.setUrl(env.getRequiredProperty("spring.datasource.auth.url"));
source.setUsername(env.getRequiredProperty("spring.datasource.auth.username"));
source.setPassword(env.getRequiredProperty("spring.datasource.auth.password"));
return source;
}
@Bean(name = "entityManagerFactorySecondary")
@Qualifier("entityManagerFactorySecondary")
public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary() {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
// 配置資料來源
factory.setDataSource(secondaryDataSource());
// VendorAdapter
factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
// entity包掃描路徑
factory.setPackagesToScan(env.getRequiredProperty("packages.to.scan.auth"));
// jpa屬性
factory.setJpaProperties(hibernateProperties());
factory.afterPropertiesSet();
return factory;
}
@Bean(name="transactionManagerSecondary")
@Qualifier("transactionManagerSecondary")
public PlatformTransactionManager transactionManagerSecondary() {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(entityManagerFactorySecondary().getObject());
return manager;
}
}
實體類:從來沒有配置過多對一,多對多這種對映關係,花了很多時間,也報了各種錯,最後總算可以運行了,實體類比較多,這裡舉簡單的幾個例子: 1.一對多的單向,涉及到兩張表,tb_auth_org_dic_type (一)和 tb_auth_org(多)表,表tb_auth_org中的orgtype對應表tb_auth_org_dic_type中的主鍵id。 TbAuthOrgDicType類:
@Entity
@Table(name="TB_AUTH_ORG_DIC_TYPE")
public class TbAuthDicOrgType implements java.io.Serializable{
private static final long serialVersionUID = 1L;
@Id
private String id;//主鍵
@Column(name = "type")
private String type;//類別
... Set和Get方法省略
}
TbAuthOrganization類:
@Entity
@Table(name="TB_AUTH_ORG")
public class TbAuthOrganization implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long orgid;
@Column(name = "ORGNAME")
private String orgname; //機構名稱
@Column(name = "ORGCODE")
private String orgcode; //編碼
@Column(name = "CHECKDATE")
@Type(type="timestamp")
private Date checkDate;//稽核日期
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "orgtype")
private TbAuthDicOrgType tbAuthDicOrgType; //機構型別
... Set和Get方法省略
}
2.一對多雙向:涉及兩張表,tb_auth_unit(一)和 tb_auth_user(多)表,tb_auth_user表中的unitid對應 tb_auth_unit表中的主鍵 id。
TbAuthUnit類:
@Entity
@Table(name="TB_AUTH_UNIT")
public class TbAuthUnit implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long unitid;
@Column(name = "SJBMMC")
private String unitname;
...
// 一對多,一個單位對應多個使用者資訊
@OneToMany(fetch=FetchType.LAZY,mappedBy="tbAuthUnit")
private Set<TbAuthUser> tbAuthUsers = new HashSet<TbAuthUser>(0);
... Set和Get方法省略
}
TbAuthUser類:
@Entity
@Table(name="TB_AUTH_USER")
public class TbAuthUser implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long userid;
@Column(name = "USERNAME")
private String username;
...
//多對一,多個使用者對應一個單位
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "UNITID")
private TbAuthUnit tbAuthUnit;
... Set和Get方法省略
}
3.多對多雙向:涉及兩張表:tb_auth_role 和 tb_auth_resource 表,會生成一張中間表 tb_auth_role_resource,roleid對應tb_auth_role中的主鍵roleid,resid對應tb_auth_resource 表中的主鍵resid。 TbAuthRole類:
@Entity
@Table(name="TB_AUTH_ROLE")
public class TbAuthRole implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long roleid;
@Column(name = "rolename")
private String rolename;
//多對多,一個角色對應多個選單資源 (TB_AUTH_ROLE_RESOURCE中間表)
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "TB_AUTH_ROLE_RESOURCE",joinColumns = { @JoinColumn(name = "ROLEID") },inverseJoinColumns = { @JoinColumn(name = "RESID") })
private Set<TbAuthResource> tbAuthResources = new HashSet<>(0);
... Set和Get方法省略
}
TbAuthResource類:
@Entity
@Table(name="TB_AUTH_RESOURCE")
public class TbAuthResource implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long resid;
@Column(name = "resname")
private String resname;
...
//多對多 一個選單資源對多個角色(TB_AUTH_ROLE_RESOURCE中間表)
@ManyToMany(mappedBy = "tbAuthResources",fetch=FetchType.LAZY)
private Set<TbAuthRole> tbAuthRoles = new HashSet<>(0);
... Set和Get方法省略
}
Dao層: 1.簡單的單個JPA查詢:使用@Query查詢
UserRepository類:
@Repository
public interface UserRepository extends JpaRepository<TbAuthUser, Long> {
@Query("select t from TbAuthUser t where t.loginname = :loginname and t.psw=:psw")
public List<TbAuthUser> checkLogin(@Param("loginname")String loginName, @Param("psw")String passWord);
@Query("select t from TbAuthUser t where t.loginname = :loginname")
public TbAuthUser getUserByLoginName(@Param("loginname")String loginName);
//修改
@Modifying
@Query("update TbAuthUser t set t.psw=:psw where t.loginname=:loginname")
public int changePassword(@Param("loginname")String loginName, @Param("psw")String passWord);
@Query("select t from TbAuthUser t where t.userid=:userid")
public TbAuthUser getById(@Param("userid")Long id);
}
2.複雜查詢,需要按照傳統的方式來寫SQL語句,就要直接使用EntityManager。 SQLDao類:
@Repository
public class SQLDao {
//如果連線的是主資料庫,則不需要加UnitName
@PersistenceContext(unitName = "entityManagerFactorySecondary")
private EntityManager em;
public String selectBySql() {
String sql="select t.username from tb_auth_user t where t.loginname=?";
List<String> list = em.createNativeQuery(sql).setParameter(1, "xxx").getResultList();
return list.get(0).toString();
}
service層:如果需要加事務管理,則在方法上加註解@Transactional,如果不是主資料來源,則需要加@Qualifier(“transactionManagerSecondary”)
@Service
public class BaseService {
@Autowired
private SQLDao sqlDao;
public String findByYYName() {
return this.sqlDao.selectBySql();
}
}
control層:
@Controller
public class IndexActiontest {
@Autowired
private BaseService baseService;
@RequestMapping("/test")
@Transactional
@Qualifier("transactionManagerSecondary")
public String hello(Model model){
String name = baseService.findByYYName();
model.addAttribute("name",name);
return "test";
}
}
前臺用的是html5,不怎麼會寫,就不介紹了。 主要遇到的一些問題: 1.打印出SQL語句中的繫結引數 解決:網上有用log4jdbc-log4j2解決的,但是我怎麼配都不顯示繫結的引數。因為用了druid,就用這個框架配合log4j來列印SQL語句以及繫結引數了。在log4j.properties配置檔案中加上log4j.logger.druid.sql.Statement= DUBUG,以及在PrimaryConfig類中加上source.addFilters(“log4j”);詳細見上面的程式碼,如果要列印輔資料庫相應的SQL語句,可以在另一個配置檔案中也加上這行程式碼。
log4j.properties配置檔案
# LOG4J配置
log4j.rootCategory=INFO, stdout, file
# 控制檯輸出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
# root日誌輸出到檔案
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.file=d:/data/logs/springboot-log4j-all.log
log4j.appender.file.DatePattern='.'yyyy-MM-dd
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
# 按不同package進行輸出
# com.micai包下的日誌配置
log4j.category.com.micai=DEBUG, didifile
# com.micai下的日誌輸出
log4j.appender.didifile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.didifile.file=d:/data/logs/springboot-log4j-my.log
log4j.appender.didifile.DatePattern='.'yyyy-MM-dd
log4j.appender.didifile.layout=org.apache.log4j.PatternLayout
log4j.appender.didifile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L ---- %m%n
# ERROR級別輸出到特定的日誌檔案中
log4j.logger.error=errorfile
# error日誌輸出
log4j.appender.errorfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorfile.file=d:/data/logs/springboot-log4j-error.log
log4j.appender.errorfile.DatePattern='.'yyyy-MM-dd
log4j.appender.errorfile.Threshold = ERROR
log4j.appender.errorfile.layout=org.apache.log4j.PatternLayout
log4j.appender.errorfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %5p %c{1}:%L - %m%n
#druid列印SQL語句
log4j.logger.druid.sql.Statement=DEBUG
2.實體類對應關係中懶載入的問題 解決:在查詢的時候,報了 org.hibernate.LazyInitializationException: could not initialize proxy - no Session,網上查了一些資料,比較好的做法是使用OpenSessionInViewFilter,這種方法是將session交給servlet filter來管理,每當一個請求來之後就會開啟一個session,只有當響應結束才會關閉。原來沒有用springboot的時候我在web.xml中配置之後就會起作用,但是在springboot中怎麼改都不起作用,我想既然是session關閉了,那麼我在control層中加事務控制是不是一樣的效果,嘗試之後可以起作用。感覺這個方法有點笨,但是目前我還沒有找到別的解決辦法。 3.JPA複雜sql查詢,需要直接使用EntityManager。因為有兩個資料來源,如果不是主資料來源,就需要加限定。@PersistenceContext(unitName = “entityManagerFactorySecondary”)
沒有解決的問題: 1.hibernate.hbm2ddl.auto=update 為什麼會執行兩遍生成表的sql語句,所以每次啟動的時候都會有報錯資訊,但是能正常啟動。