搭建Spring Boot2.X整合Hibernate5專案,並整合傳統SSH老專案的安全認證元件,以Spring Boot方式開發專案並整合到老系統

語言: CN / TW / HK

highlight: an-old-hope theme: cyanosis


持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第9天,點選檢視活動詳情

場景

由於老專案(SSH架構)需要新新增功能模組,習慣了Spring Boot快速開發,突然使用XML那一套東西,不是不可以,但是不爽,故想使用Spring Boot方式開發新專案整合到老系統。

同時,記錄、分享在搭建、整合方面遇到的問題。

可行性分析

1.能否整合?

傳統專案(SSH架構)那一套就是XML的配置,Spring Boot則是將XML簡化,也無非類似,且新Spring Boot專案核心是提供介面被呼叫。故只要能保證開發的Spring Boot專案能成功整合到SSH老系統即可。

2.安全如何認證?

老專案存在許可權認證元件,新開發的Spring Boot專案肯定得做整合,由老系統的認證元件進行安全認證。老系統的安全認證元件是非SpringBoot開發,但是以Jar的形式提供使用,最大問題在於相關配置檔案中的物件的初始化以及相容性問題需要考慮。

3.如何進行 ?

由於老系統使用Hibernate,所以首先就需要搭建Spring Boot整合Hibernate專案。

Hibernate沒有Spring Boot的相關啟動器,做整合稍顯麻煩,但是萬變不離其中,畢竟Spring Boot都是從xml轉變過來的。

其次在搭建好的環境基礎上引入老系統的安全認證元件進行測試。

搭建Spring Boot整合Hibernate5專案

新增依賴

JPA的預設實現是Hibernate,直接引入spring-boot-starter-data-jpa啟動器即可。

bash <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>

Entity

建立一個User物件,與資料庫資料表對映。 java @Entity @Data @Table(name="user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private Integer age; }

Dao

注意:低版本的Hibernate(應該是5以下)使用sessionFactory物件獲取會話物件,高版本使用的是EntityManagerFactory物件。由於老專案肯定使用sessionFactory物件,所以整合測試也使用sessionFactory物件,此時sessionFactory是有警告。 ```java public interface IUserDao {

/**
 * @description: 儲存使用者
 **/
void saveUser(User user);

User getUser(Integer id);

}

@Repository public class UserDaoImpl implements IUserDao { @Autowired private SessionFactory sessionFactory;

Session getCurrentSession() {
    return sessionFactory.getCurrentSession();
}

@Override
public void saveUser(User user) {
    getCurrentSession().save(user);
}

@Override
public User getUser(Integer id) {
    return getCurrentSession().get(User.class, id);
}

} 高版本中這樣獲取會話物件:java @Autowired private EntityManagerFactory entityManagerFactory;

    Session getCurrentSession() {
        return entityManagerFactory.unwrap(SessionFactory.class).getCurrentSession();
}

```

Service

```java public interface UserService {

void mySave(User user);

User  getUser(Integer id);

}

@Transactional(rollbackFor = Exception.class) @Service public class UserServiceImpl implements UserService {

 @Autowired
 private IUserDao userDao;

@Override
public void mySave(User user) {
    userDao.saveUser(user);
}

@Override
public User getUser(Integer id) {
    return userDao.getUser(id);
}

} ```

Controller

```java @RestController @RequestMapping("/test") public class Controller {

@Autowired
private UserService userService;

/**
 * 新增
 */
@RequestMapping("/insert")
public String insert() {
    User user = new User();
    user.setName("小白");
    user.setAge(22);
    userService.mySave(user);
    return "success";
}

 /**
 * 查詢
 */
@RequestMapping("/get")
public User getUser(Integer id) {
    return userService.getUser(id);
}

} ```

配置application.properties

```java server.port=8888

----------------------資料庫配置------------------------------

spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 ```

啟動類

排除HibernateJpa相關的自動配置,進行手動配置,故需排除HibernateJpaAutoConfiguration自動配置類,否則報錯如下: java org.springframework.orm.jpa.EntityManagerHolder cannot be cast to org.springframework.orm.hibernate5.SessionHolder

```java @SpringBootApplication(exclude = {HibernateJpaAutoConfiguration.class}) public class DemoApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

} ```

配置Hibernate

手動配置配置會話工廠與配置事務管理器,具體程式碼參考如下: ```java @Configuration public class HibernateConfig {

@Autowired
private DataSource dataSource;


/**
 * 配置會話工廠
 */
@Bean(name = "sessionFactory")

// @Bean(name="entityManagerFactory") public SessionFactory sessionFactoryBean() throws IOException { LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean(); // 設定資料來源 sessionFactoryBean.setDataSource(dataSource); // entity包路徑 sessionFactoryBean.setPackagesToScan("cn.ybzy.demo.entity"); // 配置hibernate屬性 Properties properties = new Properties(); // sql方言 properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); // 自動建立|更新|驗證資料庫表結構 properties.setProperty("hibernate.hbm2ddl.auto", "update"); // 輸出sql到控制檯 properties.setProperty("hibernate.show_sql", "true"); // 列印漂亮的sql properties.setProperty("hibernate.format_sql", "true"); properties.setProperty("hibernate.current_session_context_class", "org.springframework.orm.hibernate5.SpringSessionContext"); sessionFactoryBean.setHibernateProperties(properties); sessionFactoryBean.afterPropertiesSet(); SessionFactory sessionFactory = sessionFactoryBean.getObject();

    return sessionFactory;
}


/**
 * 配置事務管理器
 */
@Bean(name = "transactionManager")
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
    HibernateTransactionManager transactionManager = new HibernateTransactionManager();
    // 設定 sessionFactory
    transactionManager.setSessionFactory(sessionFactory);
    return transactionManager;
}

/**
 * 建立攔截器,配置事務攔截方式
 * 指定事務管理器和設定事務屬性
 *
 * @return
 */

// @Bean // public TransactionInterceptor transactionInterceptors(HibernateTransactionManager transactionManager) { // TransactionInterceptor transInterceptor = new TransactionInterceptor(); // // 設定事務管理器 // transInterceptor.setTransactionManager(transactionManager); // // 設定方法事務屬性 // Properties props = new Properties(); // props.setProperty("save", "PROPAGATION_REQUIRED"); // props.setProperty("update", "PROPAGATION_REQUIRED"); // props.setProperty("delete", "PROPAGATION_REQUIRED"); // props.setProperty("find", "PROPAGATION_REQUIRED,readOnly"); // props.setProperty("get", "PROPAGATION_REQUIRED,readOnly"); // transInterceptor.setTransactionAttributes(props); // return transInterceptor; // } // // / // * AOP攔截配置 // * 建立一個自動代理Bean的物件 // / // @Bean // public BeanNameAutoProxyCreator beanNameAutoProxyCreator() { // BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator(); // // 設定應該自動被代理包裝的 bean 的名稱 // beanNameAutoProxyCreator.setBeanNames("*ServiceImpl"); // // 設定常用攔截器 // beanNameAutoProxyCreator.setInterceptorNames("transactionInterceptor"); // return beanNameAutoProxyCreator; // }

} ```

進行測試

1.執行新增測試 在這裡插入圖片描述 2.執行查詢測試 在這裡插入圖片描述 3.執行新增異常事務回滾測試 ```java Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.

Mon Aug 15 14:44:05 CST 2022 There was an unexpected error (type=Internal Server Error, status=500). / by zero java.lang.ArithmeticException: / by zero at cn.ybzy.demo.service.impl.MyUserServiceImpl.saveUser(MyUserServiceImpl.java:25) at cn.ybzy.demo.service.impl.MyUserServiceImpl$$FastClassBySpringCGLIB$$f77af9f1.invoke() at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691) at cn.ybzy.demo.service.impl.MyUserServiceImpl$$EnhancerBySpringCGLIB$$a2877662.saveUser() ``` 當上述測試驗證操作完成後進行安全認證元件整合。

整合安全認證元件

整合分析

由於新增的安全認證元件涉及到了2個xml配置檔案 ```java spring-shiro.xml

spring-datasource-jedis.xml ```

xml檔案裡面建立的Bean需要通過Spring Boot的方式新增到容器中

解決方案:

1.使用@ImportResource註解將原生配置檔案引入,進行自動配置其中定義的Bean

2.參考xml中的配置,將xml配置轉換成類配置

開始整合

這裡使用第一種方案。由於引入元件中定義了一些物件,如service類 Dao類,這些物件需要加入到Spring容器中,所以使用@ComponentScan(basePackages = {"com.XX"})方式進行掃描 java @ImportResource(locations = {"classpath:spring-shiro.xml","classpath:spring-datasource-jedis.xml"}) @ComponentScan(basePackages = {"com.XXX"}) @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

啟動報錯

```java Description:

Field sessionFactory in com.zhunda.base.dao.impl.UserDaoImpl required a bean of type 'org.hibernate.SessionFactory' that could not be found.

The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:

Consider defining a bean of type 'org.hibernate.SessionFactory' in your configuration. ``` 原因如下:

進行Spring掃描時,發現老專案Dao層中的需要SessionFactory物件,畢竟需要該物件操作資料庫,但是此刻配置HibernateConfig物件未生效,故找不到

解決方案:將@ComponentScan(basePackages = {"com.XXX"})從啟動類移動到HibernateConfig類 ```java @Configuration @ComponentScan(basePackages = {"com.XXX"}) public class HibernateConfig {

} 再次出現異常:java Caused by: org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userDaoImpl' for bean class [com.zhunda.base.dao.impl.UserDaoImpl] conflicts with existing, non-compatible bean definition of same name and class [cn.ybzy.demo.repository.impl.UserDaoImpl] ``` 原因:

認證元件中定義有UserDaoImpl類與新專案中定義的測試UserDaoImpl類衝突,故將Spring Boot專案中的相關的測試物件,如UserDaoImpl改個名稱

```java public interface IMyUserDao {

/**
 * @description: 儲存使用者
 **/
void saveUser(User user);

}

@Repository public class MyUserDaoImpl implements IMyUserDao { @Autowired private SessionFactory sessionFactory;

Session getCurrentSession() {
    return sessionFactory.getCurrentSession();
}


@Override
public void saveUser(User user) {
    getCurrentSession().save(user);
}

} 啟動成功java 2022-08-05 14:49:52.611 INFO 3048 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8888 (http) with context path '' 2022-08-05 14:49:52.613 INFO 3048 --- [ restartedMain] o.s.s.quartz.SchedulerFactoryBean : Starting Quartz Scheduler now 2022-08-05 14:49:52.613 INFO 3048 --- [ restartedMain] org.quartz.core.QuartzScheduler : Scheduler quartzScheduler_$_NON_CLUSTERED started. 2022-08-05 14:49:52.626 INFO 3048 --- [ restartedMain] s.a.ScheduledAnnotationBeanPostProcessor : No TaskScheduler/ScheduledExecutorService bean found for scheduled processing 2022-08-05 14:49:52.635 INFO 3048 --- [ restartedMain] cn.ybzy.demo.DemoApplication : Started DemoApplication in 7.301 seconds (JVM running for 8.798) ```

測試驗證

接著測試許可權認證是否正常。由於認證元件使用Shiro,故此處Shiro報錯,整合認證元件出現異常

java org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration. 經過一番折騰,在配置delegatingFilterProxy物件即可解決 ```java @ImportResource(locations = {"classpath:spring-shiro.xml","classpath:spring-datasource-jedis.xml"}) @SpringBootApplication public class DemoApplication {

public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
}

@Bean
public ShiroFilterFactoryBean delegatingFilterProxy(@Qualifier("shiroFilter") ShiroFilterFactoryBean shiroFilterFactoryBean) {
    return shiroFilterFactoryBean;
}

}

啟動專案,執行測試,成功攔截 ![在這裡插入圖片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e338726a81d445aeb15e7e28128c2522~tplv-k3u1fbpfcp-zoom-1.image) 訪問登入介面,得到安全元件設定的cookie值:java set-cookie SSO-SESSIONID=eb22cc107b094fb2ae5884a8df7923f4; Path=/; HttpOnly; SameSite=lax ``` 將cookie值新增到整合專案對應的瀏覽器中 在這裡插入圖片描述 執行新增操作 在這裡插入圖片描述

執行查詢操作 在這裡插入圖片描述 執行事務操作 java @Override public void mySave(User user) { userDao.saveUser(user); int a=1/0; } 程式異常,同時資料庫事務生效。 在這裡插入圖片描述

使用JPA功能

Dao

建立相關介面繼承CrudRepository物件 java @Repository public interface IUserRepository extends CrudRepository<User, Integer> {}

Service

```java @Transactional(rollbackFor = Exception.class) @Service public class UserServiceImpl implements UserService {

@Autowired
private IUserDao userDao;

@Autowired
private IUserRepository userRepository;


@Override
public void saveUser(User user) {

// userDao.saveUser(user); userRepository.save(user); int a = 1 / 0; }

@Override
public User getUser(Integer id) {

// return userDao.getUser(id); Optional optional = userRepository.findById(id); return optional.get(); } } ```

配置application.properties

```java server.port=8888

----------------------資料庫配置------------------------------

spring.datasource.url=jdbc:mysql://localhost:3306/demo?characterEncoding=utf-8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456

----------------------JPA配置------------------------------

資料庫型別

spring.jpa.database=MySQL

列印sql語句

spring.jpa.show-sql=true

生成SQL表

spring.jpa.generate-ddl=true

自動修改表結構

spring.jpa.hibernate.ddl-auto=update ```

啟動專案

啟動報錯: ```java Description:

Field userRepository in cn.ybzy.demo.service.impl.UserServiceImpl required a bean named 'entityManagerFactory' that could not be found.

The injection point has the following annotations: - @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:

Consider defining a bean named 'entityManagerFactory' in your configuration. ```

預設情況下,JPA通過名稱entityManagerFactory搜尋sessionFactory

手動設定:方法上新增 @Bean(name="entityManagerFactory")

自動設定:方法名字一定要是entityManagerFactory

Hibernate配置

```java /* * 配置會話工廠 / // @Bean(name = "sessionFactory") @Bean(name="entityManagerFactory") public SessionFactory sessionFactoryBean() throws IOException { LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean(); // 設定資料來源 sessionFactoryBean.setDataSource(dataSource); // entity包路徑 sessionFactoryBean.setPackagesToScan("cn.ybzy.demo.entity"); // 配置hibernate屬性 Properties properties = new Properties(); // sql方言 properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); // 自動建立|更新|驗證資料庫表結構 properties.setProperty("hibernate.hbm2ddl.auto", "update"); // 輸出sql到控制檯 properties.setProperty("hibernate.show_sql", "true"); // 列印漂亮的sql properties.setProperty("hibernate.format_sql", "true"); properties.setProperty("hibernate.current_session_context_class", "org.springframework.orm.hibernate5.SpringSessionContext"); sessionFactoryBean.setHibernateProperties(properties); sessionFactoryBean.afterPropertiesSet(); SessionFactory sessionFactory = sessionFactoryBean.getObject();

    return sessionFactory;
}

```

獲取會話

在JPA中通常使用EntityManagerFactory物件獲取會話。 ```java @Autowired private EntityManagerFactory entityManagerFactory;

    Session getCurrentSession() {
        return entityManagerFactory.unwrap(SessionFactory.class).getCurrentSession();
}

```

執行測試

最後進行測試,JPA功能正常。