springspring-booth2

How to set the default JDBC URL value in H2 console using spring boot


I have enable the H2 console in spring boot. However, when I open the console connection page the default url is the one staved in the H2 console history. How can i configure the project to populate the URL to be the same as spring.datasource.url on project start? Currently I set the url in the console manually but I would like to have it setup automatically by the project itself.

yaml:

spring:
  h2:
    console:
      enabled: true
      path: /admin/h2


  datasource:
    url: jdbc:h2:mem:foobar

update: I know that the last connection settings are saved to ~/.h2.server.properties but what I need is to set the properties from the starting application potentially, potentially switching between several of them


Solution

  • There is no hook provided to fill in settings.

    The good news is that we can change that with a bit of code.

    Current state

    The Login screen is created in WebApp.index()

    String[] settingNames = server.getSettingNames();
    String setting = attributes.getProperty("setting");
    if (setting == null && settingNames.length > 0) {
        setting = settingNames[0];
    }
    String combobox = getComboBox(settingNames, setting);
    session.put("settingsList", combobox);
    ConnectionInfo info = server.getSetting(setting);
    if (info == null) {
        info = new ConnectionInfo();
    }
    session.put("setting", PageParser.escapeHtmlData(setting));
    session.put("name", PageParser.escapeHtmlData(setting));
    session.put("driver", PageParser.escapeHtmlData(info.driver));
    session.put("url", PageParser.escapeHtmlData(info.url));
    session.put("user", PageParser.escapeHtmlData(info.user));
    return "index.jsp";
    

    We want to tap into server.getSettingNames(), and precisely into server.getSettings() used underneath.

    synchronized ArrayList<ConnectionInfo> getSettings() {
        ArrayList<ConnectionInfo> settings = new ArrayList<>();
        if (connInfoMap.size() == 0) {
            Properties prop = loadProperties();
            if (prop.size() == 0) {
                for (String gen : GENERIC) {
                    ConnectionInfo info = new ConnectionInfo(gen);
                    settings.add(info);
                    updateSetting(info);
                }
            } else {
                for (int i = 0;; i++) {
                    String data = prop.getProperty(Integer.toString(i));
                    if (data == null) {
                        break;
                    }
                    ConnectionInfo info = new ConnectionInfo(data);
                    settings.add(info);
                    updateSetting(info);
                }
            }
        } else {
            settings.addAll(connInfoMap.values());
        }
        Collections.sort(settings);
        return settings;
    }
    

    The plan

    The Code

    @EnableConfigurationProperties({H2ConsoleProperties.class, DataSourceProperties.class})
    @Configuration
    public class H2Config {
    
        private final H2ConsoleProperties h2ConsoleProperties;
    
        private final DataSourceProperties dataSourceProperties;
    
        public H2Config(H2ConsoleProperties h2ConsoleProperties, DataSourceProperties dataSourceProperties) {
            this.h2ConsoleProperties = h2ConsoleProperties;
            this.dataSourceProperties = dataSourceProperties;
        }
    
        @Bean
        public ServletRegistrationBean<WebServlet> h2Console() {
            String path = this.h2ConsoleProperties.getPath();
            String urlMapping = path + (path.endsWith("/") ? "*" : "/*");
            ServletRegistrationBean<WebServlet> registration = new ServletRegistrationBean<>(
                    new CustomH2WebServlet(this.dataSourceProperties.getUrl()), urlMapping);
            H2ConsoleProperties.Settings settings = this.h2ConsoleProperties.getSettings();
            if (settings.isTrace()) {
                registration.addInitParameter("trace", "");
            }
            if (settings.isWebAllowOthers()) {
                registration.addInitParameter("webAllowOthers", "");
            }
            return registration;
        }
    }
    
    
    package org.h2.server.web;
    
    import javax.servlet.ServletConfig;
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Enumeration;
    
    public class CustomH2WebServlet extends WebServlet {
    
        private final String dbUrl;
    
        public CustomH2WebServlet(String dbUrl) {
            this.dbUrl = dbUrl;
        }
    
        @Override
        public void init() {
            ServletConfig config = getServletConfig();
            Enumeration<?> en = config.getInitParameterNames();
            ArrayList<String> list = new ArrayList<>();
            while (en.hasMoreElements()) {
                String name = en.nextElement().toString();
                String value = config.getInitParameter(name);
                if (!name.startsWith("-")) {
                    name = "-" + name;
                }
                list.add(name);
                if (value.length() > 0) {
                    list.add(value);
                }
            }
            String[] args = list.toArray(new String[0]);
            WebServer server = new CustomH2WebServer(dbUrl);
            server.setAllowChunked(false);
            server.init(args);
            setServerWithReflection(this, server);
        }
    
        private static void setServerWithReflection(final WebServlet classInstance, final WebServer newValue) {
            try {
                final Field field = WebServlet.class.getDeclaredField("server");
                field.setAccessible(true);
                field.set(classInstance, newValue);
            }
            catch (SecurityException|NoSuchFieldException|IllegalArgumentException|IllegalAccessException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    
    package org.h2.server.web;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    class CustomH2WebServer extends WebServer {
    
        private final String connectionInfo;
    
        CustomH2WebServer(String dbUrl) {
    
            this.connectionInfo = "Test H2 (Embedded)|org.h2.Driver|" +dbUrl+"|sa";
        }
    
        synchronized ArrayList<ConnectionInfo> getSettings() {
            ArrayList<ConnectionInfo> settings = new ArrayList<>();
            ConnectionInfo info = new ConnectionInfo(connectionInfo);
            settings.add(info);
            updateSetting(info);
            Collections.sort(settings);
            return settings;
        }
    }
    
    spring.h2.console.enabled=false
    spring.datasource.url=jdbc:h2:mem:foobar
    
    

    Everything went smoolthly, except of one private field that needed to be set via reflection.

    The code provided works with H2 1.4.199