JAVA调用MySQL递归查询相关的存储过程引发的思考

4 篇文章 0 订阅

一、前言

在实际开发中,偶遇一个需求,需要用MySQL查询树状结构数据,由于MySQL没有特定语法,因此决定采用存储过程或者函数实现。

方案变更

起初决定用函数进行递归查询,但是MySQL的函数无法执行动态SQL,后改为用存储过程实现。

二、代码实现

(2.1)起始方案:通过查询出所求的id串的集合串,在用这些id串查询出需要的结果集

过程名: get_child_nodes
入参: 表名,id字段名,父id字段名,父id值,所需查询层级数
出参: 所有id用 “,” 所拼的串

CREATE PROCEDURE `get_child_nodes`
  (IN `tableName` VARCHAR(100),
    IN `idKey` VARCHAR(50),
    IN `pIdKey` VARCHAR(50),
    IN `pId` VARCHAR(100),
    IN 'queryLevel' INT,
    OUT `result` VARCHAR(10000))

BEGIN
  SET result = '';
  SET nowLevel = 0;
  SET @sTempChd = pId;
  #将子id用逗号间隔拼成串写入变量@sTempChd
  SET @sqlcmd = CONCAT('SELECT group_concat(',idKey,') INTO @sTempChd FROM ',tableName,' where FIND_IN_SET(',pIdKey,',@sTempChd)>0;');

  WHILE @sTempChd is not null AND nowLevel < queryLevel DO
    SET nowLevel = nowLevel + 1;
    IF result != '' THEN
      SET result = concat(result,',',@sTempChd);
    ELSE
      SET result = @sTempChd;
    END IF;
    PREPARE stmt FROM @sqlcmd;
    EXECUTE stmt;
  END WHILE;
  select result;
END

JAVA端:采用EntityManager的调用存储过程查询的方式
优点:规范了出参,入参分别是什么
缺点:调用繁琐

@Service
public class OrganizationServiceImpl{
	@PersistenceContext
	private EntityManager em;
	public List<Organization> getChildrenOrgs(String id, int level){
		StoredProcedureQuery query = em.createStoredProcedureQuery ("get_child_nodes")
	                .registerStoredProcedureParameter ("tableName",String.class,ParameterMode.IN)
	                .registerStoredProcedureParameter ("idKey",String.class,ParameterMode.IN)
	                .registerStoredProcedureParameter ("pIdKey",String.class,ParameterMode.IN)
	                .registerStoredProcedureParameter ("pId",String.class,ParameterMode.IN)
	                .registerStoredProcedureParameter ("queryLevel",Integer.class,ParameterMode.IN)
	                .registerStoredProcedureParameter("result",String.class, ParameterMode.OUT)
	                .setParameter("tableName", "t_organization")
	                .setParameter("idKey", "id")
	                .setParameter("pIdKey", "parent_id")
	                .setParameter("pId", id)
	                .setParameter("queryLevel", level);
		query.execute ();
		String a = (String)query.getOutputParameterValue ("result");
		//省略通过a查询出所需的organizations的过程...
		return organizations;
	}
}
(2.2)优化方案:由于之前的方案需要和数据库交互两次,本方案在过程中就直接查出结果集

过程名: get_child_nodes
入参: 表名,id字段名,父id字段名,父id值,所需查询层级数
出参:

CREATE DEFINER=`root`@`%` PROCEDURE `get_child_nodes`(IN `tableName` VARCHAR(100),
    IN `idKey` VARCHAR(50),
    IN `pIdKey` VARCHAR(50),
    IN `pId` VARCHAR(100),
    IN `queryLevel` INT)
BEGIN
  SET @result = '';
  SET @nowLevel = 0;
  SET @sTempChd = pId;
  #将子id用逗号间隔拼成串写入变量@sTempChd
  SET @sqlcmd = CONCAT('SELECT group_concat(',idKey,') INTO @sTempChd FROM ',tableName,' where FIND_IN_SET(',pIdKey,',@sTempChd)>0;');
  SET @tableData = CONCAT('SELECT * FROM ',tableName,' where FIND_IN_SET(id,@result)>0;');
  
  WHILE @sTempChd is not null AND @nowLevel <= queryLevel DO
    SET @nowLevel = @nowLevel + 1;
    IF @result != '' THEN
    	#这步的意义在于将第一步中存放于@result的顶级父id清空
		IF @nowLevel = 2 THEN
			SET @result = '';
		END IF;
      SET @result = concat(@result,',',@sTempChd,',');
    ELSE
      SET @result = @sTempChd;
    END IF;
    PREPARE stmt FROM @sqlcmd;
    EXECUTE stmt;
  END WHILE;
  #@nowLevel > 1 意为第一步查询成功,说明成功找到子节点
  IF @nowLevel > 1 THEN
	PREPARE stmt FROM @tableData;
	EXECUTE stmt;
  END IF;
END

JAVA端:
①用JdbcTemplate创建存储过程(在应用启动时调用),这样可以不用维护创建存储过程的数据库脚本。
②新建一个StoredProceduresHelper存储过程辅助类,利用泛型调用存储过程相关方法。
优点:抽象出具体逻辑,便于维护。调用简洁易懂,便于扩展。
缺点:创建脚本在服务每次启动时都会调用一次,若同名会抛出异常。因为不想数据库单独设立一张表来存放是否含有该存储过程的信息,因此采用该方式。

辅助类:

@Conditional({GeneralServiceImplConditional.class, MysqlConditional.class})
@Component
public class StoredProceduresHelper {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @PersistenceContext
    private EntityManager em;

    private Logger logger = LoggerFactory.getLogger (StoredProceduresHelper.class);

    /**
     * 创建查询树状结构的存储过程
     */
    @Transactional
    public void createViewTreeDataProcedure(){
        String sql = "CREATE DEFINER=`root`@`%` PROCEDURE `get_child_nodes`(IN `tableName` VARCHAR(100),\n" +
                "    IN `idKey` VARCHAR(50),\n" +
                "    IN `pIdKey` VARCHAR(50),\n" +
                "    IN `pId` VARCHAR(100),\n" +
                "    IN `queryLevel` INT)\n" +
                "BEGIN\n" +
                "  SET @result = '';\n" +
                "  SET @nowLevel = 0;\n" +
                "  SET @sTempChd = pId;\n" +
                "  SET @sqlcmd = CONCAT('SELECT group_concat(',idKey,') INTO @sTempChd FROM ',tableName,' where FIND_IN_SET(',pIdKey,',@sTempChd)>0;');\n" +
                "  SET @tableData = CONCAT('SELECT * FROM ',tableName,' where FIND_IN_SET(id,@result)>0;');\n" +
                "  \n" +
                "  WHILE @sTempChd is not null AND @nowLevel <= queryLevel DO\n" +
                "    SET @nowLevel = @nowLevel + 1;\n" +
                "    IF @result != '' THEN\n" +
                "    IF @nowLevel = 2 THEN\n" +
                "      SET @result = '';\n" +
                "    END IF;\n" +
                "      SET @result = concat(@result,',',@sTempChd,',');\n" +
                "    ELSE\n" +
                "      SET @result = @sTempChd;\n" +
                "    END IF;\n" +
                "    PREPARE stmt FROM @sqlcmd;\n" +
                "    EXECUTE stmt;\n" +
                "  END WHILE;\n" +
                "  IF @nowLevel > 1 THEN\n" +
                "    PREPARE stmt FROM @tableData;\n" +
                "    EXECUTE stmt;\n" +
                "  END IF;\n" +
                "END";
        try{
            jdbcTemplate.execute (sql);
            logger.info ("get_child_nodes存储过程创建成功!");
        }catch (Exception e){
            logger.info (e.getMessage ());
        }
    }
	//通过泛型,传入类型参数的方式进行抽象
    public <T> List<T> queryViewTreeDataProcedure(String tableName, String idKey, String pIdKey, String pId, int level, Class clazz){
        List<T> result;
        try{
        	//这种方式就能获得存储过程中select...from...的结果集了
            result = em.createNativeQuery ("{call get_child_nodes(?,?,?,?,?)}", clazz)
                    .setParameter(1, tableName)
                    .setParameter(2, idKey)
                    .setParameter(3, pIdKey)
                    .setParameter(4, pId)
                    .setParameter (5, level)
                    .getResultList ();
        }catch(Exception e){
            logger.warn (e.getMessage ());
            result = Collections.EMPTY_LIST;
        }
        return result;
    }
}

调用处:精简了相当一部分代码

@Service
public class OrganizationServiceImpl{
	@Autowired
	private StoredProceduresHelper storedProceduresHelper;
	public List<Organization> getChildrenOrgs(String id, int level){
		List<Organization> organizations = storedProceduresHelper
			.queryViewTreeDataProcedure("t_organization","id","parent_id",id,level);
		return organizations;
	}
}

三、开发过程所遇到的问题

参考存储过程辅助类的相关代码:

@Conditional({GeneralServiceImplConditional.class, MysqlConditional.class})
@Component
public class StoredProceduresHelper {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    //...
    /**
     * 创建查询树状结构的存储过程
     */
    @Transactional
    public void createViewTreeDataProcedure(){//...}

    public <T> List<T> queryViewTreeDataProcedure(String tableName, String idKey, String pIdKey, String pId, int level, Class clazz){//...}
}
(3.1)使用静态方法,语句块

起初想要使用静态语句块(static{...})来实现创建存储过程的相关逻辑。结果注入进来的jdbcTemplate为null,静态语句块是同类一块加载的,在成员变量注入之前,因此为null。
解决方法: @Autowired 用在构造方法上,这样jdbcTemplate会同类一同加载,静态方法可以调用到,静态语句块仍然取不到对象。

@Conditional({GeneralServiceImplConditional.class, MysqlConditional.class})
@Component
public class StoredProceduresHelper {
    private static JdbcTemplate jdbcTemplate;
	@Autowired
	public StoredProceduresHelper (JdbcTemplate jdbcTemplate){
		StoredProceduresHelper .jdbcTemplate = jdbcTemplate;
	}
	public static void test(){
        System.out.println (jdbcTemplate);//调用时对象已被注入
    }
	public static void test(){
		System.out.println (jdbcTemplate);//null
	}
}
(3.2)使用new对StoredProceduresHelper进行实例化

测试时,new StoredProceduresHelper(),对其方法进行测试,发现成员变量均为null,未注入。

解决方法: 通过@Autowired注入StoredProceduresHelper

结论: new 的对象无法将spring容器对象注入自身(@Value,@Component,@Controller…)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Funnee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值