1. 程式人生 > >第一個springboot+jpa+oracle+maven專案

第一個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語句,所以每次啟動的時候都會有報錯資訊,但是能正常啟動。