maven引入
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-neo4jartifactId>
dependency>
uri配置
#neo4j
spring.data.neo4j.uri = bolt://neo4j:7687
spring.data.neo4j.username= neo4j
spring.data.neo4j.password= 123456
事务配置
使用multiTransactionManager这个事务管理器,可以让mysql和neo4j的的修改同时提交或者回滚
package com.zhibi.malling.aop;
import com.zhibi.malling.annotation.EnableNeo4j;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
import org.springframework.data.transaction.ChainedTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
/**
* @author QinHe at 2019-05-31
*/
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
//@Aspect
@Configuration
@DependsOn("sessionFactory")
@ConditionalOnBean(annotation = EnableNeo4j.class)
//@EnableTransactionManagement
public class TransactionAspect {
ThreadLocal<TransactionStatus> transactionStatusThreadLocal = new ThreadLocal<>();
/**
* 定义mysql事务管理器,必须有transactionManager作为默认事务管理器
*
* @param emf
* @return
*/
@Bean("transactionManager")
@Primary
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
/**
* 定义neo4j事务管理器
*
* @param sessionFactory
* @return
*/
@Bean("neo4jTransactionManager")
public Neo4jTransactionManager neo4jTransactionManager(SessionFactory sessionFactory) {
return new Neo4jTransactionManager(sessionFactory);
}
// @Autowired
// @Qualifier("neo4jTransactionManager")
// Neo4jTransactionManager neo4jTransactionManager;
// @Autowired
// @Qualifier("transactionManager")
// JpaTransactionManager jpaTransactionManager;
@Autowired
@Bean(name = "multiTransactionManager")
public PlatformTransactionManager multiTransactionManager(
Neo4jTransactionManager neo4jTransactionManager,
JpaTransactionManager mysqlTransactionManager) {
return new ChainedTransactionManager(
neo4jTransactionManager, mysqlTransactionManager);
}
// @Around("@annotation(com.zhibi.malling.annotation.MultiTransaction)")
// public Object multiTransaction(ProceedingJoinPoint proceedingJoinPoint) {
// TransactionStatus neo4jTransactionStatus = neo4jTransactionManager.getTransaction(new DefaultTransactionDefinition());
// TransactionStatus jpaTransactionStatus = jpaTransactionManager.getTransaction(new DefaultTransactionDefinition());
// try {
// Object obj = proceedingJoinPoint.proceed();
// //注意:事务之间必须是包含关系,不能交叉
// jpaTransactionManager.commit(jpaTransactionStatus);
// neo4jTransactionManager.commit(neo4jTransactionStatus);
// return obj;
// } catch (Throwable throwable) {
// jpaTransactionManager.rollback(jpaTransactionStatus);
// neo4jTransactionManager.rollback(neo4jTransactionStatus);
// throw new RuntimeException(throwable);
// }
// }
}
节点实体类orm
package com.zhibi.malling.user.spi.neo4j;
import com.alibaba.fastjson.JSONObject;
import com.zhibi.malling.user.spi.enums.BonusConfig;
import com.zhibi.malling.utils.DateUtil;
import io.swagger.annotations.ApiModelProperty;
import org.neo4j.ogm.annotation.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.*;
/**
* @author QinHe at 2019-05-27
*/
@NodeEntity(label = "User")
public class UserProfile implements Serializable {
private static final long serialVersionUID = 3870028896024217898L;
@Id
@GeneratedValue
private Long id;
@Property(name = "userId")
@Index(unique = true)
private Long userId;
@Property(name = "phone")
private String phone;
@Property(name = "nickname")
private String nickname;
@Property(name = "mallingBonusRole")
@ApiModelProperty("mallingBonusRole(1=JP,2=SP,3=EP)")
private Byte mallingBonusRole;
@Property(name = "marginPaid")
@ApiModelProperty("marginPaid(1=已交保证金)")
private Byte marginPaid;
@Property(name = "fastStartBonusRoleLastMonth")
@ApiModelProperty("上个月FB级别(S/M/L)")
private Byte fastStartBonusRoleLastMonth;
@Property(name = "fastStartBonusRole")
@ApiModelProperty("fastStartBonusRole(S/M/L)")
private Byte fastStartBonusRole;
@Property(name = "fastStartCheckTime")
@ApiModelProperty("FastStart按钮点击时间,转换成yyyyMMddHHmmss格式,以Long型数据展示")
private Long fastStartCheckTime;
@Property(name = "unilevelBonusRole")
@ApiModelProperty("unilevelBonusRole(L1..L7)")
private Byte unilevelBonusRole;
@Property(name = "depth")
private Long depth;
@Property(name = "version")
@Version
private Long version;
@Transient
private JSONObject other = new JSONObject();
public UserProfile() {
}
public enum MallingBonusRoleEnum {
COMMON((byte) 0),
JP((byte) 1),
SP((byte) 2),
EP((byte) 3);
private Byte code;
MallingBonusRoleEnum(Byte code) {
this.code = code;
}
public Byte getCode() {
return code;
}
}
public enum FastStartBonusRoleEnum {
COMMON((byte) 0, (byte) 1, BigDecimal.ZERO),
S((byte) 1, (byte) 2, BonusConfig.S_LEVEL_CONDITION, EarningsLevelForFBEnum.LEVEL_1, EarningsLevelForFBEnum.LEVEL_2),
M((byte) 2, (byte) 3, BonusConfig.M_LEVEL_CONDITION, EarningsLevelForFBEnum.LEVEL_1, EarningsLevelForFBEnum.LEVEL_2, EarningsLevelForFBEnum.LEVEL_3),
L((byte) 3, null, BonusConfig.L_LEVEL_CONDITION, EarningsLevelForFBEnum.LEVEL_1, EarningsLevelForFBEnum.LEVEL_2, EarningsLevelForFBEnum.LEVEL_3, EarningsLevelForFBEnum.LEVEL_4);
private static Map<Byte, FastStartBonusRoleEnum> map = new HashMap<>();
static {
for (FastStartBonusRoleEnum fastStartBonusRoleEnum : FastStartBonusRoleEnum.values()) {
map.put(fastStartBonusRoleEnum.getCode(), fastStartBonusRoleEnum);
}
}
private Byte code;
private Byte nextRoleCode;
private BigDecimal pvNeed;
private EarningsLevelForFBEnum[] levelEnums;
FastStartBonusRoleEnum(Byte code, Byte nextRoleCode, BigDecimal pvNeed, EarningsLevelForFBEnum... bonusLevels) {
this.code = code;
this.nextRoleCode = nextRoleCode;
this.pvNeed = pvNeed;
this.levelEnums = bonusLevels;
}
public static FastStartBonusRoleEnum getEnumByCode(Byte code) {
return map.get(code);
}
public Byte getCode() {
return code;
}
public Byte getNextRoleCode() {
return nextRoleCode;
}
public BigDecimal getPvNeed() {
return pvNeed;
}
public EarningsLevelForFBEnum[] getLevelEnums() {
return levelEnums;
}
}
public enum UnilevelBonusRoleEnum {
COMMON((byte) 0),
L1((byte) 1, EarningsLevelForUBEnum.LEVEL_1),
L2((byte) 2, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1),
L3((byte) 3, EarningsLevelForUBEnum.LEVEL_3, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1),
L4((byte) 4, EarningsLevelForUBEnum.LEVEL_4, EarningsLevelForUBEnum.LEVEL_3, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1),
L5((byte) 5, EarningsLevelForUBEnum.LEVEL_5, EarningsLevelForUBEnum.LEVEL_4, EarningsLevelForUBEnum.LEVEL_3, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1),
L6((byte) 6, EarningsLevelForUBEnum.LEVEL_6, EarningsLevelForUBEnum.LEVEL_5, EarningsLevelForUBEnum.LEVEL_4, EarningsLevelForUBEnum.LEVEL_3, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1),
L7((byte) 7, EarningsLevelForUBEnum.LEVEL_7, EarningsLevelForUBEnum.LEVEL_6, EarningsLevelForUBEnum.LEVEL_5, EarningsLevelForUBEnum.LEVEL_4, EarningsLevelForUBEnum.LEVEL_3, EarningsLevelForUBEnum.LEVEL_2, EarningsLevelForUBEnum.LEVEL_1);
private static Map<Byte, UnilevelBonusRoleEnum> map = new HashMap<>();
static {
for (UnilevelBonusRoleEnum unilevelBonusRoleEnum : UnilevelBonusRoleEnum.values()) {
map.put(unilevelBonusRoleEnum.getCode(), unilevelBonusRoleEnum);
}
}
private Byte code;
private EarningsLevelForUBEnum[] levelEnums;
UnilevelBonusRoleEnum(Byte code, EarningsLevelForUBEnum... levelEnums) {
this.code = code;
this.levelEnums = levelEnums;
}
public static UnilevelBonusRoleEnum getEnumByCode(Byte code) {
return map.get(code);
}
public Byte getCode() {
return code;
}
public EarningsLevelForUBEnum[] getLevelEnums() {
return levelEnums;
}
}
public enum EarningsLevelForFBEnum {
LEVEL_1(1, BonusConfig.DIRECTLY_LEVEL1_INCOME),
LEVEL_2(2, BonusConfig.DIRECTLY_LEVEL2_INCOME),
LEVEL_3(3, BonusConfig.DIRECTLY_LEVEL3_INCOME),
LEVEL_4(4, BonusConfig.DIRECTLY_LEVEL4_INCOME),
;
private static Map<Integer, EarningsLevelForFBEnum> map = new HashMap<>();
static {
for (EarningsLevelForFBEnum earningsLevelForFBEnum : EarningsLevelForFBEnum.values()) {
map.put(earningsLevelForFBEnum.getCode(), earningsLevelForFBEnum);
}
}
private Integer code;
private BigDecimal bonusPercentage;
EarningsLevelForFBEnum(Integer code, BigDecimal bonusPercentage) {
this.code = code;
this.bonusPercentage = bonusPercentage;
}
public static EarningsLevelForFBEnum getEnumByCode(Integer code) {
return map.get(code);
}
public Integer getCode() {
return code;
}
public BigDecimal getBonusPercentage() {
return bonusPercentage;
}
}
public enum EarningsLevelForUBEnum {
LEVEL_1(1, BonusConfig.UNILEVEL_L1, BonusConfig.UNILEVEL_LEVEL1_LEVEL4_INCOME),
LEVEL_2(2, BonusConfig.UNILEVEL_L2, BonusConfig.UNILEVEL_LEVEL1_LEVEL4_INCOME),
LEVEL_3(3, BonusConfig.UNILEVEL_L3_L7, BonusConfig.UNILEVEL_LEVEL1_LEVEL4_INCOME),
LEVEL_4(4, BonusConfig.UNILEVEL_L3_L7, BonusConfig.UNILEVEL_LEVEL1_LEVEL4_INCOME),
LEVEL_5(5, BonusConfig.UNILEVEL_L3_L7, BonusConfig.UNILEVEL_LEVEL5_INCOME),
LEVEL_6(6, BonusConfig.UNILEVEL_L3_L7, BonusConfig.UNILEVEL_LEVEL6_INCOME),
LEVEL_7(7, BonusConfig.UNILEVEL_L3_L7, BonusConfig.UNILEVEL_LEVEL7_INCOME);
private static Map<Integer, EarningsLevelForUBEnum> map = new HashMap<>();
static {
for (EarningsLevelForUBEnum earningsLevelForUBEnum : EarningsLevelForUBEnum.values()) {
map.put(earningsLevelForUBEnum.getCode(), earningsLevelForUBEnum);
}
}
private Integer code;
private BigDecimal pvCondition;
private BigDecimal bonusPercentage;
EarningsLevelForUBEnum(Integer code, BigDecimal pvCondition, BigDecimal bonusPercentage) {
this.code = code;
this.pvCondition = pvCondition;
this.bonusPercentage = bonusPercentage;
}
public static EarningsLevelForUBEnum getEnumByCode(Integer code) {
return map.get(code);
}
public Integer getCode() {
return code;
}
public BigDecimal getPvCondition() {
return pvCondition;
}
public BigDecimal getBonusPercentage() {
return bonusPercentage;
}
}
}
关系实体类
package com.zhibi.malling.user.spi.neo4j;
import com.alibaba.fastjson.JSONObject;
import org.neo4j.ogm.annotation.*;
import java.io.Serializable;
import java.util.Date;
/**
* @author QinHe at 2019-05-27
*/
@RelationshipEntity(type = "Invite")
public class InviteRelation implements Serializable {
private static final long serialVersionUID = 3870028896024217898L;
@Id
@GeneratedValue
private Long id;
/**
* 定义关系的起始节点 == StartNode
*/
@StartNode
private UserProfile startNode;
/**
* 定义关系的终止节点 == EndNode
*/
@EndNode
private UserProfile endNode;
private Date createTime;
private Long inviterId;
private Long inviteeId;
@Transient
private JSONObject other = new JSONObject();
@Property(name = "version")
@Version
private Long version;
public InviteRelation() {
}
public JSONObject getOther() {
return other;
}
public void setOther(JSONObject other) {
this.other = other;
}
public UserProfile getStartNode() {
return startNode;
}
public void setStartNode(UserProfile startNode) {
this.startNode = startNode;
}
public UserProfile getEndNode() {
return endNode;
}
public void setEndNode(UserProfile endNode) {
this.endNode = endNode;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Long getInviterId() {
return inviterId;
}
public void setInviterId(Long inviterId) {
this.inviterId = inviterId;
}
public Long getInviteeId() {
return inviteeId;
}
public void setInviteeId(Long inviteeId) {
this.inviteeId = inviteeId;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
}
Jpa的Repository
package com.zhibi.malling.repository.neo4j;
import com.zhibi.malling.user.spi.neo4j.UserProfile;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author QinHe at 2019-05-27
*/
public interface UserNeo4jRepository extends Neo4jRepository<UserProfile, Long> {
UserProfile findByUserId(Long userId);
@Query(" match(inviter:User)-[r:Invite*1..]->(invitee:User) where inviter.userId = {inviterId} return invitee")
List<UserProfile> getAllInvitees(@Param("inviterId") Long inviterId);
@Query(" match(invitee:User)<-[r:Invite*1..]-(inviter:User) where invitee.userId = {inviteeId} return inviter")
List<UserProfile> getAllInviters(@Param("inviteeId") Long inviteeId);
@Query(" match(invitee:User)<-[r:Invite]-(inviter:User) where invitee.userId = {inviteeId} return inviter")
UserProfile getInviter(@Param("inviteeId") Long inviteeId);
@Query(" match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {inviterId} return count(invitee.userId)")
long countDirectByInviterId(@Param("inviterId") Long inviterId);
@Query(" match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {inviterId} and invitee.mallingBonusRole in {inviteeRoleList} return count(invitee.userId)")
long countDirectByInviterIdAndInviteeRole(@Param("inviterId") Long inviterId, @Param("inviteeRoleList") List<Byte> inviteeRoleList);
@Query(" match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {inviterId} and invitee.mallingBonusRole={inviteeRole} return count(invitee.userId)")
long countDirectByInviterIdAndInviteeRole(@Param("inviterId") Long inviterId, @Param("inviteeRole") Byte inviteeRole);
@Query(" match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {inviterId} return count(invitee.userId)")
UserProfile getNearestInviter(@Param("inviterId") Long inviterId);
@Query(value = " match(inviter:User)-[r:Invite*0..]->(invitee:User) where inviter.userId = {inviterId} and invitee.userId in {userIdList}" +
" and invitee.totalDirectInviteJpCount<{mallingBonusJpUpgradeSpCondition} return invitee order by totalDirectInviteJpCount desc ",
countQuery = " match(inviter:User)-[r:Invite*0..]->(invitee:User) where inviter.userId = {inviterId} and invitee.userId in {userIdList}" +
" and invitee.totalDirectInviteJpCount<{mallingBonusJpUpgradeSpCondition} return count(invitee) ")
Page<UserProfile> getInviteesForUpgradeEp(@Param("legId") Long legId, @Param("userIdList") List<Long> userIdList, Integer mallingBonusJpUpgradeSpCondition, Pageable pageable);
@Query(" match(inviter:User)-[r1:Invite]->(leg:User)-[r2:Invite*0..]->(invitee:User) where inviter.userId = {inviterId} and invitee.userId={userId} return leg ")
UserProfile findLeg(@Param("inviterId") Long inviterId, @Param("userId") Long userId);
@Query("match(inviter:User)-[r:Invite*]->(invitee:User) where inviter.userId = {userId} and not (invitee)-->() RETURN count(invitee)")
Long getFarthestInviteeCount(@Param("userId") Long userId);
@Query("match(inviter:User)-[r:Invite*]->(invitee:User) where inviter.userId = {userId} and not (invitee)-->() RETURN invitee order by invitee.id skip {skip} limit {limit}")
List<UserProfile> getFarthestInvitee(@Param("userId") Long userId, @Param("skip") Long skip, @Param("limit") Long limit);
@Query("match(head:User{userId:{headUserId}}), (farthest:User{userId:{farthestUserId}}), p=shortestPath((head)-[Invite*]-(farthest)) RETURN p")
List<UserProfile> getInviteePath(@Param("headUserId") Long headUserId, @Param("farthestUserId") Long farthestUserId);
@Query(value = "match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {userId} return invitee order by invitee.userId desc",
countQuery = "match(inviter:User)-[r:Invite]->(invitee:User) where inviter.userId = {userId} return count(invitee.userId)")
Page<UserProfile> getDirectInviteePage(@Param("userId") Long userId, Pageable pageable);
@Query("match(inviter:User)-[r:Invite*]->(invitee:User) where inviter.userId = {userId} return count(invitee.userId)")
long countAllInvitee(@Param("userId") Long userId);
@Query("match(:User{userId:{topUserId}})-[:Invite*1]->(leg:User)-[:Invite*0..]-> (:User{userId:{childUserId}}) return leg")
UserProfile getLegUser(@Param("topUserId") Long headUserId, @Param("childUserId") Long childUserId);
}
事务的使用:
@Transactional(rollbackFor = Exception.class, value = "multiTransactionManager")
使用原生的session进行复杂查询
@Override
public List<UserProfile> getAllInvitees(Long inviterId, Long depth, List<Byte> mallingBonusRoleList,
List<Byte> fastStartBonusRoleList, List<Byte> unilevelBonusRoleList, Integer limit) {
if (inviterId == null) {
throw new CommonException("inviteeId is null");
}
String cql = "match(inviter:User)-[r:Invite*1..{depth}]->(invitee:User) where inviter.userId = {inviterId} ";
if (depth != null) {
cql = cql.replace("{depth}", depth.toString());
} else {
cql = cql.replace("{depth}", "");
}
cql = cql.replace("{inviterId}", inviterId.toString());
if (mallingBonusRoleList != null && mallingBonusRoleList.size() > 0) {
cql += " and invitee.mallingBonusRole in " + createInCypher(mallingBonusRoleList);
}
if (fastStartBonusRoleList != null && fastStartBonusRoleList.size() > 0) {
cql += " and invitee.fastStartBonusRole in " + createInCypher(fastStartBonusRoleList);
}
if (unilevelBonusRoleList != null && unilevelBonusRoleList.size() > 0) {
cql += " and invitee.unilevelBonusRole in " + createInCypher(unilevelBonusRoleList);
}
cql += " return invitee ";
if (limit != null) {
cql += " limit " + limit;
}
Session session = SessionFactoryUtils.getSession(sessionFactory);
Iterable<UserProfile> query = session.query(UserProfile.class, cql, Maps.newHashMap());
return Lists.newArrayList(query);
}
@Override
public String createInCypher(List<?> list) {
if (list != null && list.size() > 0) {
StringBuilder stringBuilder = new StringBuilder("[");
list.forEach(o -> {
if (o != null) {
if (o instanceof String) {
stringBuilder.append("\"").append(o).append("\",");
} else {
stringBuilder.append(o).append(",");
}
}
});
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
stringBuilder.append("]");
return stringBuilder.toString();
}
return null;
}
异常信息解决
在使用@Transactional(rollbackFor = Exception.class, value = “multiTransactionManager”)作为nei4j的事务管理的时候,commit时报异常:
org.neo4j.ogm.exception.TransactionManagerException: "Transaction is not current for this thread"
原因:因为springdataneo4j支持的cypher语句里面不支持depth,所以需要手动用原生的session来执行cql,在开启session时,使用了新的session而不是当前方法中当前线程的事务中的session,所以解决办法:
Session session = sessionFactory.openSession();
替换为:
Session session = SessionFactoryUtils.getSession(sessionFactory);