解決neo4j @Transactional 與Spring data jpa @Transactional 沖突問題,@CreatedBy,@CreatedDate,@LastModifiedBy,@LastModifiedDate,以及解決@Version失效問題


 之前mybatis特別流行,所以前幾個項目都是用@SelectProvider,@InsertProvider,@UpdateProvider,@DeleteProvider 加反射泛型封裝了一些通用方法,雖然小伙伴表示使用得比較滿意,但是我認為對他們的發展不太好,可以學習一些主流的大廠框架.同時也為把Spring cloud 升級為Finchley.M8,Spring boot 升級為 2.0 ,所以又看了一遍Spring data JPA,之前看了Spring data jpa 覺得 Specification 可讀性特別不好,也特別不好理解,就沒有深究了,這次再回頭看Spring data jpa 加入了一些可以細化控制的鎖機制,而且用了Spring data mongo后,也挺喜歡對象持久化監聽事件,並且對事務注解的增強(剛好最近使用Neo4J做權限模塊,這解決了TransactionManager沖突的問題)

現在想了一下也是愚蠢,我們應該吸收Spring data jpa 優秀的思想,使用Spring data 快速開發,選擇性地放棄一些功能,用Mybatis管理長sql,揚長避短,所以整合一了Mybatis + jpa的抽象通用service,怎么封裝抽象整合Mybatis,這里就不講了

mongo版本 http://www.cnblogs.com/sweetchildomine/p/7729319.html

完整的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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.lzw</groupId>
    <artifactId>web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
<name>web</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.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> <spring-cloud.version>Finchley.M8</spring-cloud.version> <skipTests>true</skipTests> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.cloud</groupId>--> <!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>--> <!--</dependency>--> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- redis session --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency> <!-- jbatis begin --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>org.lzw</groupId> <artifactId>jbatis</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <!-- jbatis end --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>

首先建立一個抽象類

/**
 * Created by laizhenwei on 2018/3/21
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Accessors(chain = true)
@MappedSuperclass
public abstract class AbstractJbatisEntity implements Serializable {
    private static final long serialVersionUID = 8413473756947412760L;

    @Id
    @GenericGenerator(name="idGenerator", strategy="uuid") //這個是hibernate的注解/生成32位UUID
    @GeneratedValue(generator="idGenerator")
    private String id;

}

再建立一個包含@CreatedBy,@CreatedDate,@LastModifiedBy,@LastModifiedDate注解的抽象類 @Version 不寫在父類里,並不是每個表都需要樂觀鎖,哪個表需要,就加在某個pojo里

/**
 * Created by laizhenwei on 2018/3/21
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@MappedSuperclass
@Accessors(chain = true)
@EntityListeners(AuditingEntityListener.class)
public class AbstractAuditingEntity extends AbstractJbatisEntity {

    @CreatedBy
    private String creator;

    @CreatedDate
    private LocalDateTime createTime;

    @LastModifiedBy
    private String modifier;

    @LastModifiedDate
    private LocalDateTime modifyTime;

}

實現AuditorAware,監聽實體持久化操作,以便從Spring Security 的ContextHolder里面獲取當前發起操作的用戶信息

/**
 * Created by laizhenwei on 2018/3/21
 */
public class SpringSecurityAuditorAware  implements AuditorAware<String> {

    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()|| SecurityUtils.isAnonymous())
            return Optional.of(SecurityUtils.ANONYMOUSUSER);
        return Optional.ofNullable(((UserInfo) authentication.getPrincipal()).getUsername());
    }
}

配置類這里之所以注釋掉@EnableTransactionManagement,是因為在Neo4j配置類里已經存在此注解

 
         
/**
 * Created by laizhenwei on 2018/3/21
 */
@Configuration
@EnableJpaAuditing
@EnableJpaRepositories(basePackages="org.lzw.web.repository.jbatis")
//@EnableTransactionManagement
public class JpaConfig{

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Bean
    public AuditorAware<String> auditorProvider() {
        return new SpringSecurityAuditorAware();
    }

    @Bean
    public JpaTransactionManager transactionManager(){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }

}

Neo4j配置類,可見兩個TransactionManager  Bean 雖然類型不同,但是名字也不能相同,否則注入時會沖突,並且必須有其中一個名字為transactionManager,否則也會報找不到默認的TransactionManager  Bean

 
         
/**
 * Created by laizhenwei on 2018/3/21
 */
@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(basePackages="org.lzw.web.repository.neo4j")
public class Neo4jPersistenceContext {

    @Bean
    public SessionFactory getSessionFactory() {
        SessionFactory sessionFactory = new SessionFactory(configuration(),"org.lzw.web.domain.neo4j");
        return sessionFactory;
    }

    @Bean
    public Neo4jTransactionManager neo4jTransactionManager(){
        return new Neo4jTransactionManager(getSessionFactory());
    }

    @Bean
    public org.neo4j.ogm.config.Configuration configuration() {
        return new org.neo4j.ogm.config.Configuration.Builder()
                .connectionLivenessCheckTimeout(50)
                .connectionPoolSize(100)
                .verifyConnection(true)
//                .autoIndex("update")
                .credentials("neo4j","root")
                .uri("bolt://192.168.1.207:7687")
                .build();
    }

}
userinfo pojo @Version
/**
 * Created by laizhenwei on 2018/3/21
 */
@Entity
@Table(name = "tenant_user_info")
@Alias("UserInfo")
public class UserInfo extends AbstractAuditingEntity implements UserDetails {

    private String username;

    private String password;

    private boolean accountNonExpired;

    private boolean accountNonLocked;

    private boolean credentialsNonExpired;

    private boolean enabled;


    @Version
    private Long version;

    @Transient
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    public UserInfo setUsername(String username) {
        this.username = username;
        return this;
    }

    public UserInfo setPassword(String password) {
        this.password = password;
        return this;
    }

    public UserInfo setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
        return this;
    }

    public UserInfo setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
        return this;
    }

    public UserInfo setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
        return this;
    }

    public UserInfo setEnabled(boolean enabled) {
        this.enabled = enabled;
        return this;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof UserInfo))
            return false;
        if (o.toString().equals(this.getUsername()))
            return true;
        return false;
    }

    @Override
    public int hashCode() {
        return this.getUsername().hashCode();
    }

    @Override
    public String toString() {
        return this.getUsername();
    }

}

 




注解也被增強@Transactional(transactionManager="transactionManager",propagation = Propagation.REQUIRED) ,可以指定transactionManager,來解決多個transactionManager的問題.

@Version 樂觀鎖,之前再使用Spring data mongo 的時候,感覺很不錯.但是開始在Spring data jpa 里面死活無效,version 字段一直為null,百思不得其解,再Spring data jpa 官方文檔 居然搜索不到@Version注解,以為被棄用了
后來折騰了很久發現必須使用
@javax.persistence.Version 可以生效
而使用
@org.springframework.data.annotation.Version 不生效

因為Spring data mongo 就是用Spring的注解,並且認為Spring 都是遵守J2EE 標准的,潛意識覺得用兩個注解都可以生效.
后來想想,@Version這樂觀鎖,是Hibernate實現的,而Hibernate才是Jpa規范的鼻祖,那么如果沒有增加對@org.springframework.data.annotation.Version的支持,應該只會認@javax.persistence.Version
 
        
 
        
 
        

測試類,然后去看看是否版本號,以及創建人,創建時間,修改人,修改時間都有了

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserInfoServiceTest {

    @Resource
    private UserInfoService userInfoService;

    @Test
    @Rollback(false)
    @Transactional(transactionManager="transactionManager",propagation = Propagation.REQUIRED)
    public void saveTest(){
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("athos");
        userInfo.setPassword("1232456");
        userInfo.setEnabled(true);
        userInfo.setCredentialsNonExpired(true);
        userInfo.setAccountNonExpired(true);
        userInfo.setAccountNonLocked(true);
        userInfoService.saveAndFlush(userInfo);
        UserInfo userInfo1 = userInfoService.getOne(userInfo.getId());
        userInfo1.setPassword("123123");
        userInfoService.saveAndFlush(userInfo1);
    }

}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM