MySQL存储过程、函数介绍及Java应用示例

声明:本文主要以运用为主,不介绍相关概念。


目录

存储过程

创建存储过程

各参数说明

调用存储过程

删除存储过程

存储过程的创建与使用示例(部分知识点,在示例中讲解)

if-else示例

case when示例

loop示例

repeat示例

while示例

函数

创建函数

各参数说明

调用函数

删除函数

函数的创建与使用示例

创建示例

使用示例

Java使用存储过程、函数(创建、运行)示例

Java创建并使用存储过程示例

Java创建并使用函数示例


欢迎来到JustryDeng的博客!正文start!


存储过程

创建存储过程:

CREATE

    [DEFINER = { user | CURRENT_USER }]

    PROCEDURE sp_name ([proc_parameter[,...]])

    [characteristic ...] routine_body

各参数说明

[]括起来的:表示是可选的。

DEFINER创建者,默认是CURRENT_USER此功能与SQL SECURITY可保证使用存储过程的权限等安全性。

sp_name:存储过程的名称。

proc_parameter:参数,其具体格式为:[ IN | OUT | INOUT ] param_name type
                              注:IN表示输入参数,OUT表示输出参数,INOUT表示即可作为输入参数又可作为输出参数。
                                     不指定的话,默认为IN。其中
type指的是任何有效的MySQL数据类型。
                              注:可以将存储过程的计算结果保存在OUT/INOUT参数里面,变相把结果返回。

characteristic:当前存储过程的特性,可设置的特性有:
                                                      COMMENT '
string' 
                                                      | LANGUAGE SQL 
                                                      | [NOT] DETERMINISTIC 
                                                      | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
                                                      | SQL SECURITY { DEFINER | INVOKER }

routine_body:具体存储过程逻辑,可以包含任何常规有效的SQL语句(即:可以包含SQL函数、增删改查等)
                          注:如果含有多条语句,那么需要在前后分别加上BEGIN、END来标识开始与结束。
                          提示:BEGIN、END可以嵌套。

调用存储过程:

CALL 存储过程名(参数...);

如:

删除存储过程:

-- 如果存储过程存在,那么进行删除
DROP PROCEDURE IF EXISTS 存储过程名;

如:

存储过程的创建与使用示例(部分知识点,在示例中讲解):

if-else示例:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS ifElseProcedure;
-- 设置存储过程以“//”为结束符
DELIMITER //
CREATE PROCEDURE ifElseProcedure ( IN paramA INT, IN paramB INT, OUT paramC VARCHAR(40) ) 
BEGIN
  IF paramA = 1 THEN set paramC = '参数paramA的值为1';
	ELSEIF paramA = 2 THEN set paramC = '参数paramA的值为2' ;
	ELSEIF paramA = paramB THEN set  paramC = '参数paramA的值 = 参数paramB的值' ;
	ELSE set  paramC = '参数有误!' ;
	END IF;
END//

DELIMITER说明:DELIMITER用于定义存储过程、函数等的结束符(默认结束符为“;”),当MySQL读取到结束符时,
                              就会认为命令已经读取完毕,可以执行了,但是在一个存储过程或者一个函数中,可能包含多
                              个“;”,如果mysql读取到第一个“;”时就执行,那么就可能导致执行失败(因为mysql还没有读取
                              到完整的命令)。使用DELIMITER就可以解决这个问题。使用方式如:DELIMITER //,那么结
                              束符就变成了“//”,那么当存储过程中出现“//”时,就代表存储过程结束了(具体使用方式如上
                              所示)。
                              追注:此时常规SQL语句的的结束符仍然是“;”。

set赋值说明:对于OUT、INOUT参数的赋值,使用set 参数A = 参数值a, 参数B = 参数值b的方式进行赋值。
                       追注:在存储过程中对IN类别的参数进行赋值,该赋值操作只在该存储过程内部有效;调用存
                                  储过程后,如果在外部观察该变量的值,会发现和原来是一样的,并没有受到存储过程
                                  中的赋值操作的影响;但是在存储过程内部来观察,会发现该值确实发生了变化。此处
                                  类似于java中的实参与形参的部分区别,其实是一种域的体现;这也解释能理解为什么
                                  会有IN、OUT、INOUT三种参数类别了。

into赋值说明:在某些情况下,我们也可以使用INTO来进行赋值,如:

使用测试:

case when示例:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS caseWhenProcedure;

-- 设置存储过程以“//”为结束符
DELIMITER //
CREATE PROCEDURE caseWhenProcedure ( IN paramA INT, INOUT paramB VARCHAR(20) ) 
BEGIN
DECLARE a,b,c VARCHAR(5) DEFAULT 'none';
CASE paramA
	WHEN 80  THEN  SET  a = 'HTTP';
	WHEN 443 THEN SET  b = 'HTTPS';
	WHEN 21  THEN SET  c = 'FTP';
	ELSE SET a = '0', b = '0', c = '0';
END CASE;
set  paramB = CONCAT(a, ',', b, ',', c);
END//

上述SQL等价于:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS caseWhenProcedure;

-- 设置存储过程以“//”为结束符
DELIMITER //
CREATE PROCEDURE caseWhenProcedure ( IN paramA INT, INOUT paramB VARCHAR(20) ) 
BEGIN
DECLARE a,b,c VARCHAR(5) DEFAULT 'none';
CASE 
	WHEN paramA =80  THEN  SET  a = 'HTTP';
	WHEN paramA =443 THEN SET  b = 'HTTPS';
	WHEN paramA =21  THEN SET  c = 'FTP';
	ELSE SET a = '0', b = '0', c = '0';
END CASE;
set  paramB = CONCAT(a, ',', b, ',', c);
END//

局部变量说明:局部变量的声明方式为:

DECLARE var_name [, var_name] ... type [DEFAULT value]

                     注:局部变量的赋值方式与参数一样,使用set进行赋值或者使用into进行赋值。
                     
注:局部变量只能在BEGIN、END之间进行定义,END结束后,局部变量失效
                     注:对于字符串的拼接可以使用CONCAT函数来完成。

使用测试:

loop示例:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS loopProcedure;

-- 设置存储过程以“//”为结束符
DELIMITER //

CREATE PROCEDURE loopProcedure ( IN paramA INT, INOUT paramB VARCHAR(100) ) 
BEGIN
DECLARE i INT DEFAULT 0;
my_label: LOOP

  -- 循环体
	SET paramB = CONCAT(paramB, i);
	
	-- 变量递增(注:此处暂不支持i++、++i的形式)
  SET i = i + 1;
	
  -- 临界条件,判断是否结束循环
	IF i > paramA THEN
		LEAVE my_label; 
	END IF; 
END LOOP;
END//

注:此处的LEAVE xxx;跳出指定循环体的概念与Java中的break xxx;跳出指定循环体的概念其实是一样的。

使用测试:

repeat示例:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS repeatProcedure;
-- 设置存储过程以“//”为结束符
DELIMITER //
CREATE PROCEDURE repeatProcedure ( IN paramA INT, INOUT paramB VARCHAR(100) ) 
BEGIN
DECLARE i INT DEFAULT 0;
REPEAT
  -- 循环体
	SET paramB = CONCAT(paramB, i);
	-- 变量递增(注:此处暂不支持i++、++i的形式)
  SET i = i + 1;
  -- 临界条件,判断是否结束循环
  UNTIL i > paramA END REPEAT;
END//

注:repeat与loop都是至少先执行一次,然后再判断是否继续循环。

使用测试:

while示例:

-- 如果存储过程已经存在,那么移除
DROP PROCEDURE IF EXISTS whileProcedure;
-- 设置存储过程以“//”为结束符
DELIMITER //
CREATE PROCEDURE whileProcedure ( IN paramA INT, INOUT paramB VARCHAR(100) ) 
BEGIN
DECLARE i INT DEFAULT 0;
-- 临界条件,判断是否结束循环
WHILE i <= 3 DO
	-- 循环体
  SET paramB = CONCAT(paramB, i);
  -- 变量递增(注:此处暂不支持i++、++i的形式)
  SET i = i + 1;
END WHILE;
END//

注:while是先判断,再决定是否继续循环,而repeat和loop都是至少先执行一次,然后再判断是否继续循环。

使用测试:


函数

创建函数

CREATE

    [DEFINER = { user | CURRENT_USER }]

    FUNCTION sp_name ([func_parameter[,...]])

    RETURNS type

    [characteristic ...] routine_body

各参数说明

[]括起来的:表示是可选的。

DEFINER创建者,默认是CURRENT_USER此功能与SQL SECURITY可保证使用存储过程的权限等安全性。

sp_name:函数的名称。

func_parameter:参数,其具体格式为param_name type
                               注此处函数与存储过程不同,函数的参数没有IN/OUT/INOUT之分(其实相当于只有输入参数)。

type:返回值的数据类型(任何有效的MySQL数据类型)。注:函数的返回值只有一个。

characteristic:当前存储过程的特性,可设置的特性有
                                                COMMENT 'string' 
                                                | LANGUAGE SQL 
                                                | [NOT] DETERMINISTIC 
                                                | { CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
                                                | SQL SECURITY { DEFINER | INVOKER }

routine_body:具体函数逻辑,可以包含任何常规有效的SQL语句(即可以包含SQL函数、增删改查等)。
                           
如果含有多条语句,那么需要在前后分别加上BEGIN、END来标识开始与结束。
                           提示
BEGIN、END可以嵌套。

调用函数:

SELECT 函数名(参数...);

如:

删除函数:

-- 如果函数存在,那么进行删除

DROP FUNCTION IF EXISTS 函数名;

如:

函数的创建与使用示例:

说明:函数与存储过程比较相似,都是对一些SQL逻辑的封装,熟悉掌握了存储过程,就能很快上手函数。
下面简单实例一下函数的创建与使用:

创建示例:

-- 如果函数已经存在,那么移除
DROP FUNCTION IF EXISTS myFunction;
-- 设置函数以“//”为结束符
DELIMITER //
CREATE FUNCTION myFunction(paramA INT, paramB INT) 
-- 返回值类型
RETURNS VARCHAR(50) 
-- 如果函数体有多条语句,那么函数体需要放在BEGIN 与 END中
BEGIN
  DECLARE diffValue INT;
  DECLARE returnValue varchar(20) DEFAULT '';
  set diffValue = paramA - paramB;
  set returnValue = CONCAT(paramA,' - ', paramB, ' = ', diffValue);
  -- 返回结果
  RETURN returnValue;
END//

使用示例:


Java使用存储过程、函数(创建、运行)示例:

P.S.作为一个程序员,我不想说太多话,直接上代码吧。

Java创建并使用存储过程示例:

涉及到的mapper中的接口有:

import com.aspire.model.ProcedureModel;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.StatementType;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * mapper注解
 *
 * 说明:灵活运用@Select等注解即可
 *
 *
 * @author JustryDeng
 * @date 2019/1/25 21:10
 */
@Mapper
public interface JustryDengMapper {

    /**
     * 查询 存储过程/函数 是否存在
     *
     * 注:mysql.proc记录的是所有数据库中的存储过程名,查询时最好指定数据库名;
     *    当然如果能保证所有数据库中的存储过程的唯一性的话,也可以不指定数据库名
     *
     * @param name
     *            存储过程/函数  的 名字
     * @param database
     *            数据库
     * @param type
     *            类型,其值只能为   PROCEDURE   或者  FUNCTION
     *
     * @return  是否存在
     * @date 2019/1/27 10:16
     */
    @Select("SELECT IF (COUNT(*)=0,FALSE,TRUE) FROM mysql.proc WHERE db=#{database} "
            + " AND `type`=#{type} AND `name`=#{name}")
    boolean procedureOrFunctionIsExist (@Param("name") String name,
                                        @Param("database") String database,
                                        @Param("type") String type);


    /**
     * 创建 存储过程/函数  的SQL语句
     *
     * @param procedureOrFunction
     *            存储过程/函数  的 创建语句
     *
     * @date 2019/1/25 21:17
     */
    @Select("${procedureOrFunction}")
    void createProcedureOrFunction (@Param("procedureOrFunction") String procedureOrFunction);


    /**
     * 调用存储过程(以map作为参数容器)
     *
     * 注:存储过程是将结果,放在OUT/INOUT参数里面,所以这里的返回值用void即可;
     *    就算是用Object接收参数,接受到的也是null
     *
     * 注:就算传入map中原本就没有OUT对应的key(这里即为keyThree),那么执行存
     *    储过程后,也会也会自动put进去
     *
     * @param paramsMap
     *            map容器,容纳 IN 、 OUT 、 INOUT参数
     *
     * @date 2019/1/27 10:35
     */
    @Select("CALL ifElseProcedure ("
            + " #{map.keyOne, mode = IN, jdbcType = NUMERIC}, "
            + " #{map.keyTwo, mode = IN, jdbcType = NUMERIC},"
            + " #{map.keyThree, mode = OUT, jdbcType = VARCHAR}"
            + ")")
    @Options(statementType = StatementType.CALLABLE)
    void callProcedureByMap (@Param("map") Map<String, Object> paramsMap);

    /**
     * 调用存储过程(以对象作为参数容器)
     *
     * 注:存储过程是将结果,放在OUT/INOUT参数里面,所以这里的返回值用void即可;
     *    就算是用Object接收参数,接受到的也是null
     *
     * 注:模型必须有相应的字段,且必须有setter、getter方法
     *
     * @param pm
     *            对象容器,容纳 IN 、 OUT 、 INOUT参数
     *
     * @date 2019/1/27 10:35
     */
    @Select("CALL ifElseProcedure ("
            + " #{paramA, mode = IN, jdbcType = NUMERIC}, "
            + " #{paramB, mode = IN, jdbcType = NUMERIC},"
            + " #{paramC, mode = OUT, jdbcType = VARCHAR}"
            + ")")
    @Options(statementType = StatementType.CALLABLE)
    void callProcedureByModel (ProcedureModel pm);

}

测试方法为:

import com.aspire.mapper.JustryDengMapper;
import com.aspire.model.ProcedureModel;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

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

    @Autowired
    JustryDengMapper mapper;

    /**
     * java程序使用MySQL存储过程示例
     *
     * @author JustryDeng
     * @date 2019/1/27 10:24
     */
    @Test
    public void useProcedureDemo() {
        final String database = "demo";
        final String procedureType = "PROCEDURE";

        // -> 判断存储过程ifElseProcedure是否存在
        boolean exist = mapper.procedureOrFunctionIsExist("ifElseProcedure",database, procedureType);

        // -> 不存在则创建存储过程
        if (!exist) {
            // 注:在java程序中创建MySQL存储过程时,直接写创建指令即可
            String procedureSql = ("CREATE PROCEDURE ifElseProcedure ( IN paramA INT, IN paramB INT, "
                                   + "OUT paramC VARCHAR(40) ) ")
                                  .concat("BEGIN ")
                                  .concat("  IF paramA = 1 THEN set paramC = '参数paramA的值为1'; ")
                                  .concat(" ELSEIF paramA = 2 THEN set paramC = '参数paramA的值为2' ; ")
                                  .concat(" ELSEIF paramA = paramB THEN set  paramC = '参数paramA的值 = 参数paramB的值' ; ")
                                  .concat(" ELSE set  paramC = '参数有误!' ; ")
                                  .concat(" END IF; ")
                                  .concat("END");
            mapper.createProcedureOrFunction(procedureSql);

        }

        // -> 组装参数、(以map为参数容器)调用存储过程
        Map<String, Object> map = new HashMap<>(4);
        map.put("keyOne", 3);
        map.put("keyTwo", 3);
        // 执行调用
        mapper.callProcedureByMap(map);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println(entry.getKey() + "\t" + entry.getValue());
        }

        // -> 组装参数、(以model为参数容器)调用存储过程
        ProcedureModel procedureModel = new ProcedureModel();
        procedureModel.setParamA(4);
        procedureModel.setParamB(5);
        // 执行调用
        mapper.callProcedureByModel(procedureModel);
        System.out.println(procedureModel);
    }

}

运行测试方法,控制台输出:

Java创建并使用函数示例:

涉及到的mapper中的接口有:

import com.aspire.model.ProcedureModel;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.mapping.StatementType;

import java.util.Map;

/**
 * mapper注解
 *
 * 说明:灵活运用@Select等注解即可
 *
 *
 * @author JustryDeng
 * @date 2019/1/25 21:10
 */
@Mapper
public interface JustryDengMapper {

    /**
     * 查询 存储过程/函数 是否存在
     *
     * 注:mysql.proc记录的是所有数据库中的存储过程名,查询时最好指定数据库名;
     *    当然如果能保证所有数据库中的存储过程的唯一性的话,也可以不指定数据库名
     *
     * @param name
     *            存储过程/函数  的 名字
     * @param database
     *            数据库
     * @param type
     *            类型,其值只能为   PROCEDURE   或者  FUNCTION
     *
     * @return  是否存在
     * @date 2019/1/27 10:16
     */
    @Select("SELECT IF (COUNT(*)=0,FALSE,TRUE) FROM mysql.proc WHERE db=#{database} "
            + " AND `type`=#{type} AND `name`=#{name}")
    boolean procedureOrFunctionIsExist (@Param("name") String name,
                                        @Param("database") String database,
                                        @Param("type") String type);


    /**
     * 创建 存储过程/函数  的SQL语句
     *
     * @param procedureOrFunction
     *            存储过程/函数  的 创建语句
     *
     * @date 2019/1/25 21:17
     */
    @Select("${procedureOrFunction}")
    void createProcedureOrFunction (@Param("procedureOrFunction") String procedureOrFunction);

    /**
     * 调用函数
     *
     * 注:调用函数时的传参方式,与普通增删改查一样,返回值的接收也是一样的
     *
     * @param paramA
     *            函数参数
     * @param paramB
     *            函数参数
     *
     * @return 函数结果
     * @date 2019/1/27 10:35
     */
    @Select("SELECT myFunction(#{a}, #{b})")
    String invokeFunction (@Param("a") Integer paramA, @Param("b") Integer paramB);
}

测试方法为:

import com.aspire.mapper.JustryDengMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

    @Autowired
    JustryDengMapper mapper;

    /**
     * java程序使用MySQL自定义函数示例
     *
     * @author JustryDeng
     * @date 2019/1/27 10:24
     */
    @Test
    public void useFunctionDemo() {
        final String database = "demo";
        final String functionType = "FUNCTION";

        // -> 判断自动以函数myFunction是否存在
        boolean exist = mapper.procedureOrFunctionIsExist("myFunction", database, functionType);

        // -> 不存在则创建函数
        if (!exist) {
            // 注:在java程序中创建函数时,直接写创建指令即可
            String functionSql = "CREATE FUNCTION myFunction(paramA INT, paramB INT) "
                                  .concat("RETURNS VARCHAR(50) ")
                                  .concat("BEGIN")
                                  .concat("  DECLARE diffValue INT;")
                                  .concat("  DECLARE returnValue varchar(20) DEFAULT '';")
                                  .concat("  set diffValue = paramA - paramB;")
                                  .concat("  set returnValue = CONCAT(paramA,' - ', paramB, ' = ', diffValue);")
                                  .concat("  RETURN returnValue;")
                                  .concat("END");
            mapper.createProcedureOrFunction(functionSql);

        }

        // -> 调用函数
        System.out.println(mapper.invokeFunction(321, 123));
    }

}

运行测试方法,控制台输出:

 

笔者寄语:

         本文只是对MySQL存储过程与函数的简单使用示例,当存储过程、函数与实际业务联系起来后,可能会产生非常复杂的逻辑,就需要在实际开发中灵活处理了。

 

^_^ 如有不当之处,欢迎指正

^_^ 参考书籍
             
《mysql8.0官方文档》

^_^ 测试项目代码托管链接:
               https://github.com/JustryDeng/CommonRepository

^_^ 本文已经被收录进《程序员成长笔记(四)》,笔者JustryDeng

猜你喜欢

转载自blog.csdn.net/justry_deng/article/details/86664916