-
maven引入
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </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);
Spring Data Neo4J 使用
最新推荐文章于 2024-06-06 19:19:07 发布