├── .circleci
└── config.yml
├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── smattme
│ ├── EmailService.java
│ ├── MysqlBaseService.java
│ ├── MysqlExportService.java
│ ├── MysqlImportService.java
│ ├── TablesResponse.java
│ ├── exceptions
│ └── MysqlBackup4JException.java
│ └── helpers
│ └── MysqlExportServiceHelper.java
└── test
├── java
└── com
│ └── smattme
│ ├── MysqlBackup4JIntegrationTest.java
│ └── MysqlBackup4JUnitTest.java
└── resources
├── mysql_init.sql
└── simplelogger.properties
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | jobs:
4 | build-and-test:
5 | docker:
6 | - image: cimg/openjdk:8.0
7 | steps:
8 | - setup_remote_docker:
9 | docker_layer_caching: true
10 | - checkout
11 | - run:
12 | name: Build
13 | command: mvn -B clean package
14 |
15 | workflows:
16 | build:
17 | jobs:
18 | - build-and-test
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | /src/test/java/com/smattme/MysqlExportServiceUnitTest.java
3 | /target/
4 | /.idea/
5 | .DS_Store
6 | /smatt_files/
7 | /external/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Seun Matt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | mysql-backup4j
2 | ==============
3 |
4 | [](https://github.com/SeunMatt/mysql-backup4j)
5 |
6 | [](https://maven-badges.herokuapp.com/maven-central/com.smattme/mysql-backup4j/badge.svg)
7 |
8 | mysql-backup4j is a library for programmatically exporting mysql databases
9 | and sending the zipped dump to email, Amazon S3, Google Drive or any other cloud storage of choice
10 |
11 | **It gives the developer access to the generated zip file and the generated SQL query string**
12 | for use in other part of the application.
13 |
14 | **It also provides a method for importing the SQL exported by the tool - programmatically.**
15 |
16 | Installation
17 | ============
18 | The artifact is available on Maven Central and can be added to the project's pom.xml:
19 |
20 | ```xml
21 |
22 | com.smattme
23 | mysql-backup4j
24 | 1.3.0
25 |
26 | ```
27 |
28 | The latest version can be found [here](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.smattme%22%20a%3A%22mysql-backup4j%22)
29 |
30 | Usage
31 | =====
32 | The minimum configuration required for the library is the database name, username and password.
33 |
34 | However, if you want the backup file to be sent to your email automatically after backup, you must
35 | provide email configurations as well.
36 |
37 | ```java
38 | //required properties for exporting of db
39 | Properties properties = new Properties();
40 | properties.setProperty(MysqlExportService.DB_NAME, "database-name");
41 | properties.setProperty(MysqlExportService.DB_USERNAME, "root");
42 | properties.setProperty(MysqlExportService.DB_PASSWORD, "root");
43 | properties.setProperty(MysqlExportService.DB_HOST, "localhost");
44 | properties.setProperty(MysqlExportService.DB_PORT, "3306");
45 |
46 | //properties relating to email config
47 | properties.setProperty(MysqlExportService.EMAIL_HOST, "smtp.mailtrap.io");
48 | properties.setProperty(MysqlExportService.EMAIL_PORT, "25");
49 | properties.setProperty(MysqlExportService.EMAIL_USERNAME, "mailtrap-username");
50 | properties.setProperty(MysqlExportService.EMAIL_PASSWORD, "mailtrap-password");
51 | properties.setProperty(MysqlExportService.EMAIL_FROM, "test@smattme.com");
52 | properties.setProperty(MysqlExportService.EMAIL_TO, "backup@smattme.com");
53 |
54 | //optional email configs
55 | properties.setProperty(MysqlExportService.EMAIL_SSL_PROTOCOLS, "TLSv1.2");
56 | properties.setProperty(MysqlExportService.EMAIL_SMTP_AUTH_ENABLED, "true");
57 | properties.setProperty(MysqlExportService.EMAIL_START_TLS_ENABLED, "true");
58 |
59 | //set the outputs temp dir
60 | properties.setProperty(MysqlExportService.TEMP_DIR, new File("external").getPath());
61 |
62 | MysqlExportService mysqlExportService = new MysqlExportService(properties);
63 | mysqlExportService.export();
64 | ```
65 |
66 | Calling `mysqlExportService.export();` will export the database and save the dump temporarily in the configured `TEMP_DIR`
67 |
68 | If an email config is supplied, the dump will be sent as an attachment. Finally, when all operations are completed the
69 | temporary dir is cleared and deleted.
70 |
71 | If you want to get the generated backup file as a Java `File` object, you need to specify this property as part of the
72 | configuration:
73 |
74 | ```java
75 | //...
76 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_ZIP, "true");
77 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_SQL_FILE, "true");
78 | ```
79 |
80 | and then you can call this method:
81 |
82 | ```java
83 | File file = mysqlExportService.getGeneratedZipFile();
84 | ```
85 |
86 | Finally, let's say for some reason you want the generated SQL string you can do this:
87 |
88 | ```java
89 | String generatedSql = mysqlExportService.getGeneratedSql();
90 | ```
91 |
92 | Other parameters are:
93 |
94 | ```java
95 | properties.setProperty(MysqlExportService.ADD_IF_NOT_EXISTS, "true");
96 | properties.setProperty(MysqlExportService.JDBC_DRIVER_NAME, "com.mysql.cj.jdbc.Driver");
97 | properties.setProperty(MysqlExportService.JDBC_CONNECTION_STRING, "jdbc:mysql://localhost:3306/database-name?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false");
98 | ```
99 |
100 | They are explained in a detailed manner in this [tutorial](https://smattme.com/blog/technology/how-to-backup-mysql-database-programmatically-using-mysql-backup4j)
101 |
102 | Importing a Database
103 | --------------------
104 | To import a database, you need to use the ImportService like so:
105 |
106 | ```java
107 | String sql = new String(Files.readAllBytes(Paths.get("path/to/sql/dump/file.sql")));
108 |
109 | boolean res = MysqlImportService.builder()
110 | .setDatabase("database-name")
111 | .setSqlString(sql)
112 | .setUsername("root")
113 | .setPassword("root")
114 | .setHost("localhost")
115 | .setPort("3306")
116 | .setDeleteExisting(true)
117 | .setDropExisting(true)
118 | .importDatabase();
119 |
120 | assertTrue(res);
121 | ```
122 |
123 | First get SQL as a String and then pass it to the import service with the right configurations.
124 |
125 | Alternatively, you can also use the `.setJdbcConnString(jdbcURL)` method on the import service.
126 |
127 | e.g.
128 | ```java
129 | boolean res = MysqlImportService.builder()
130 | .setSqlString(generatedSql)
131 | .setJdbcConnString("jdbc:mysql://localhost:3306/backup4j_test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false")
132 | .setUsername("db-username")
133 | .setPassword("db-password")
134 | .setDeleteExisting(true)
135 | .setDropExisting(true)
136 | .importDatabase();
137 | ```
138 |
139 | `setDeleteExisting(true)` will **delete all data** from existing tables in the target database.
140 |
141 | While `setDropExisting(true)` will **drop** the table.
142 |
143 | Supplying `false` to these functions will disable their respective actions.
144 |
145 |
146 | **NOTE: The import service is only guaranteed to work with SQL files generated by the export service of this library**
147 |
148 | CHANGELOG
149 | =========
150 | v1.2.1
151 | - Raises a new runtime exception `MysqlBackup4JException` if the required properties are not configured
152 |
153 | Author
154 | ======
155 | Seun Matt [smattme.com](https://smattme.com) with :green_heart:
156 |
157 | Contributions and Support
158 | =========================
159 | If you want to create a new feature, though not compulsory, but it will be helpful to reach out to me first before proceeding.
160 |
161 | To avoid a scenario where you submit a PR for an issue that someone else is working on already.
162 |
163 |
164 | Tutorials / Articles
165 | ====================
166 | - [https://smattme.com/blog/technology/how-to-backup-mysql-database-programmatically-using-mysql-backup4j](https://smattme.com/blog/technology/how-to-backup-mysql-database-programmatically-using-mysql-backup4j)
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.smattme
8 | mysql-backup4j
9 | 1.3.0
10 | jar
11 |
12 | ${project.groupId}:${project.artifactId}
13 |
14 | This is a simple library for backing up mysql databases and sending to emails, cloud storage and so on.
15 | It also provide a method for programmatically, importing SQL queries generated during the export process,
16 |
17 | https://github.com/SeunMatt/mysql-backup4j
18 |
19 |
20 |
21 | MIT License
22 | http://www.opensource.org/licenses/mit-license.php
23 |
24 |
25 |
26 |
27 |
28 | Seun Matt
29 | smatt382@gmail.com
30 | SmattMe
31 | https://smattme.com
32 |
33 |
34 |
35 |
36 | scm:git:git://github.com/SeunMatt/mysql-backup4j.git
37 | scm:git:ssh://github.com:SeunMatt/mysql-backup4j.git
38 | https://github.com/SeunMatt/mysql-backup4j/tree/master
39 |
40 |
41 |
42 | 1.20.0
43 |
44 |
45 |
46 |
47 |
48 | org.testcontainers
49 | testcontainers-bom
50 | ${testcontainers.version}
51 | pom
52 | import
53 |
54 |
55 |
56 |
57 |
58 |
59 | mysql
60 | mysql-connector-java
61 | 8.0.33
62 |
63 |
64 | org.zeroturnaround
65 | zt-zip
66 | 1.17
67 | jar
68 |
69 |
70 | javax.mail
71 | mail
72 | 1.5.0-b01
73 |
74 |
75 | org.slf4j
76 | slf4j-api
77 | 1.7.25
78 |
79 |
80 | org.slf4j
81 | slf4j-simple
82 | 1.7.25
83 | test
84 |
85 |
86 | org.junit.jupiter
87 | junit-jupiter-api
88 | 5.7.0
89 | test
90 |
91 |
92 | org.testcontainers
93 | testcontainers
94 | test
95 |
96 |
97 | org.testcontainers
98 | junit-jupiter
99 | test
100 |
101 |
102 | org.testcontainers
103 | mysql
104 | test
105 |
106 |
107 |
108 |
109 |
110 | ossrh
111 | https://oss.sonatype.org/content/repositories/snapshots
112 |
113 |
114 | ossrh
115 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
116 |
117 |
118 |
119 |
120 |
121 |
122 | org.apache.maven.plugins
123 | maven-compiler-plugin
124 | 3.1
125 |
126 | 1.8
127 | 1.8
128 |
129 |
130 |
131 | org.apache.maven.plugins
132 | maven-surefire-plugin
133 | 3.0.0-M5
134 |
135 |
136 | com.smattme.MysqlBackup4JIntegrationTest
137 |
138 |
139 |
140 |
141 | org.apache.maven.plugins
142 | maven-javadoc-plugin
143 | 3.2.0
144 |
145 |
146 | attach-javadocs
147 |
148 | jar
149 |
150 |
151 |
152 |
153 |
154 | org.apache.maven.plugins
155 | maven-source-plugin
156 | 3.0.1
157 |
158 |
159 | attach-sources
160 |
161 | jar
162 |
163 |
164 |
165 |
166 |
167 | org.apache.maven.plugins
168 | maven-gpg-plugin
169 | 1.6
170 |
171 |
172 | sign-artifacts
173 | verify
174 |
175 | sign
176 |
177 |
178 |
179 |
180 |
181 | org.sonatype.plugins
182 | nexus-staging-maven-plugin
183 | 1.6.8
184 | true
185 |
186 | ossrh
187 | https://oss.sonatype.org/
188 | false
189 |
190 |
191 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/EmailService.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import javax.mail.*;
7 | import javax.mail.internet.InternetAddress;
8 | import javax.mail.internet.MimeBodyPart;
9 | import javax.mail.internet.MimeMessage;
10 | import javax.mail.internet.MimeMultipart;
11 | import java.io.File;
12 | import java.util.Properties;
13 |
14 | /**
15 | * Created by seun_ on 25-Feb-18.
16 | *
17 | */
18 | class EmailService {
19 |
20 | private String host = "";
21 | private int port = 0;
22 | private String fromAdd = "";
23 | private String toAdd = "";
24 | private String username = "";
25 | private String password = "";
26 | private String subject = "";
27 | private String msg = "";
28 | private String sslProtocols = "TLSv1.2";
29 | private String startTlsEnabled = "true";
30 | private boolean smtpAuthEnabled = true;
31 | private File [] attachments;
32 | private Logger logger = LoggerFactory.getLogger(EmailService.class);
33 | private final String LOG_PREFIX = "java-mysql-exporter";
34 |
35 |
36 | private EmailService() {}
37 |
38 | /**
39 | * This is used to instantiate this class and form a
40 | * builder pattern
41 | * @return EmailService a new instance of this class
42 | */
43 | static EmailService builder() {
44 | return new EmailService();
45 | }
46 |
47 | EmailService setHost(String host){
48 | this.host = host;
49 | return this;
50 | }
51 |
52 | EmailService setPort(int port) {
53 | this.port = port;
54 | return this;
55 | }
56 |
57 | EmailService setFromAddress(String fromAdd) {
58 | this.fromAdd = fromAdd;
59 | return this;
60 | }
61 |
62 | EmailService setToAddress(String toAdd) {
63 | this.toAdd = toAdd;
64 | return this;
65 | }
66 |
67 | EmailService setUsername(String username) {
68 | this.username = username;
69 | return this;
70 | }
71 |
72 | EmailService setPassword(String password) {
73 | this.password = password;
74 | return this;
75 | }
76 |
77 | EmailService setSubject(String subject) {
78 | this.subject = subject;
79 | return this;
80 | }
81 |
82 | EmailService setMessage(String message) {
83 | this.msg = message;
84 | return this;
85 | }
86 |
87 | EmailService setAttachments(File[] files) {
88 | this.attachments = files;
89 | return this;
90 | }
91 |
92 | EmailService setSslProtocols(String sslProtocols) {
93 | this.sslProtocols = sslProtocols;
94 | return this;
95 | }
96 |
97 | EmailService setStartTlsEnabled(String startTlsEnabled) {
98 | this.startTlsEnabled = startTlsEnabled;
99 | return this;
100 | }
101 |
102 | EmailService setSmtpAuthEnabled(boolean smtpAuthEnabled) {
103 | this.smtpAuthEnabled = smtpAuthEnabled;
104 | return this;
105 | }
106 |
107 | /**
108 | * This will check if the necessary properties
109 | * are set for sending an email successfully
110 | * @return boolean
111 | */
112 | private boolean isPropertiesSet() {
113 | return !this.host.isEmpty() &&
114 | this.port > 0 &&
115 | !this.username.isEmpty() &&
116 | !this.password.isEmpty() &&
117 | !this.toAdd.isEmpty() &&
118 | !this.fromAdd.isEmpty() &&
119 | !this.subject.isEmpty() &&
120 | !this.msg.isEmpty() &&
121 | this.attachments != null && this.attachments.length > 0;
122 | }
123 |
124 |
125 | /**
126 | * This function will send an email
127 | * and add the generated sql file as an attachment
128 | * @return boolean
129 | */
130 | boolean sendMail() {
131 |
132 | if(!this.isPropertiesSet()) {
133 | logger.debug(LOG_PREFIX + ": Required Mail Properties are not set. Attachments will not be sent");
134 | return false;
135 | }
136 |
137 | Properties prop = new Properties();
138 | prop.put("mail.smtp.auth", true);
139 | prop.put("mail.smtp.starttls.enable", "true");
140 | prop.put("mail.smtp.host", this.host);
141 | prop.put("mail.smtp.port", this.port);
142 | prop.put("mail.smtp.ssl.trust", host);
143 | prop.put("mail.smtp.ssl.protocols", sslProtocols);
144 |
145 | logger.debug(LOG_PREFIX + ": Mail properties set");
146 |
147 | Session session = Session.getInstance(prop, new Authenticator() {
148 | @Override
149 | protected PasswordAuthentication getPasswordAuthentication() {
150 | return new PasswordAuthentication(username, password);
151 | }
152 | });
153 |
154 | logger.debug(LOG_PREFIX + ": Mail Session Created");
155 |
156 | try {
157 | // create a default mime message object
158 | Message message = new MimeMessage(session);
159 | message.setFrom(new InternetAddress(fromAdd));
160 | message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toAdd));
161 | message.setSubject(subject);
162 |
163 | // body part for message
164 | MimeBodyPart mimeBodyPart = new MimeBodyPart();
165 | mimeBodyPart.setContent(msg, "text/html");
166 |
167 | // body part for attachments
168 | MimeBodyPart attachmentBodyPart = new MimeBodyPart();
169 | logger.debug(LOG_PREFIX + ": " + this.attachments.length + " attachments found");
170 | for (File file: this.attachments) {
171 | attachmentBodyPart.attachFile(file);
172 | }
173 |
174 | // create a multipart to combine them together
175 | Multipart multipart = new MimeMultipart();
176 | multipart.addBodyPart(mimeBodyPart);
177 | multipart.addBodyPart(attachmentBodyPart);
178 |
179 | //now set the multipart as the content of the message
180 | message.setContent(multipart);
181 |
182 | // send the message
183 | Transport.send(message);
184 |
185 | logger.debug(LOG_PREFIX + ": MESSAGE SENT SUCCESSFULLY");
186 |
187 | return true;
188 |
189 | } catch (Exception e) {
190 | logger.debug(LOG_PREFIX + ": MESSAGE NOT SENT. " + e.getLocalizedMessage());
191 | e.printStackTrace();
192 | return false;
193 | }
194 |
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/MysqlBaseService.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import com.smattme.exceptions.MysqlBackup4JException;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.sql.*;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.Objects;
11 |
12 | /**
13 | * Created by seun_ on 01-Mar-18.
14 | *
15 | */
16 | public class MysqlBaseService {
17 |
18 | private static Logger logger = LoggerFactory.getLogger(MysqlBaseService.class);
19 |
20 | static final String SQL_START_PATTERN = "-- start";
21 | static final String SQL_END_PATTERN = "-- end";
22 |
23 | /**
24 | * This is a utility function for connecting to a
25 | * database instance that's running on localhost at port 3306.
26 | * It will build a JDBC URL from the given parameters and use that to
27 | * obtain a connect from doConnect()
28 | * @param username database username
29 | * @param password database password
30 | * @param database database name
31 | * @param driverName the user supplied mysql connector driver class name. Can be empty
32 | * @return Connection
33 | * @throws ClassNotFoundException exception
34 | * @throws SQLException exception
35 | */
36 | @Deprecated
37 | static Connection connect(String username, String password, String database, String driverName) throws ClassNotFoundException, SQLException {
38 | String url = "jdbc:mysql://localhost:3306/" + database + "?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false";
39 | String driver = (Objects.isNull(driverName) || driverName.isEmpty()) ? "com.mysql.cj.jdbc.Driver" : driverName;
40 | return doConnect(driver, url, username, password);
41 | }
42 |
43 | public static Connection connect(String username, String password, String host, String port, String database, String driverName) throws ClassNotFoundException, SQLException {
44 |
45 | String url = String.format("jdbc:mysql://%s:%s/%s", host, port, database);
46 | url = url + "?useUnicode=true&useJDBCCompliantTimezoneShift=true"
47 | + "&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false";
48 |
49 | String driver = (Objects.isNull(driverName) || driverName.isEmpty())
50 | ? "com.mysql.cj.jdbc.Driver"
51 | : driverName;
52 |
53 | return doConnect(driver, url, username, password);
54 | }
55 |
56 |
57 | /**
58 | * This is a utility function that allows connecting
59 | * to a database instance identified by the provided jdbcURL
60 | * The connector driver name can be empty
61 | * @param username database username
62 | * @param password database password
63 | * @param jdbcURL the user supplied JDBC URL. It's used as is. So ensure you supply the right parameters
64 | * @param driverName the user supplied mysql connector driver class name
65 | * @return Connection
66 | * @throws ClassNotFoundException exception
67 | * @throws SQLException exception
68 | */
69 | static Connection connectWithURL(String username, String password, String jdbcURL, String driverName) throws ClassNotFoundException, SQLException {
70 | String driver = (Objects.isNull(driverName) || driverName.isEmpty()) ? "com.mysql.cj.jdbc.Driver" : driverName;
71 | return doConnect(driver, jdbcURL, username, password);
72 | }
73 |
74 | /**
75 | * This will attempt to connect to a database using
76 | * the provided parameters.
77 | * On success it'll return the java.sql.Connection object
78 | * @param driver the class name for the mysql driver to use
79 | * @param url the url of the database
80 | * @param username database username
81 | * @param password database password
82 | * @return Connection
83 | * @throws SQLException exception
84 | * @throws ClassNotFoundException exception
85 | */
86 | private static Connection doConnect(String driver, String url, String username, String password) throws SQLException, ClassNotFoundException {
87 | Class.forName(driver);
88 | Connection connection = DriverManager.getConnection(url, username, password);
89 | logger.debug("DB Connected Successfully");
90 | return connection;
91 | }
92 |
93 | /**
94 | * This is a utility function to get the names of all
95 | * the tables and views that're in the database supplied
96 | * @param database the database name
97 | * @param stmt Statement object
98 | * @return TableResponse object containing the list of tables and views
99 | * @throws SQLException exception
100 | */
101 | static TablesResponse getAllTablesAndViews(String database, Statement stmt) throws SQLException {
102 |
103 | List tables = new ArrayList<>();
104 | List views = new ArrayList<>();
105 |
106 | ResultSet rs;
107 | rs = stmt.executeQuery("SHOW TABLE STATUS FROM `" + database + "`;");
108 | while ( rs.next() ) {
109 | String comment = rs.getString("Comment");
110 | if("VIEW".equals(comment)) {
111 | views.add(rs.getString("Name"));
112 | }
113 | else {
114 | tables.add(rs.getString("Name"));
115 | }
116 | }
117 |
118 | return new TablesResponse(tables, views);
119 | }
120 |
121 | /**
122 | * This function is an helper function
123 | * that'll generate a DELETE FROM database.table
124 | * SQL to clear existing table
125 | * @param database database
126 | * @param table table
127 | * @return String sql to delete the all records from the table
128 | */
129 | static String getEmptyTableSQL(String database, String table) {
130 | String safeDeleteSQL = "SELECT IF( \n" +
131 | "(SELECT COUNT(1) as table_exists FROM information_schema.tables \n" +
132 | "WHERE table_schema='" + database + "' AND table_name='" + table + "') > 1, \n" +
133 | "'DELETE FROM " + table + "', \n" +
134 | "'SELECT 1') INTO @DeleteSQL; \n" +
135 | "PREPARE stmt FROM @DeleteSQL; \n" +
136 | "EXECUTE stmt; DEALLOCATE PREPARE stmt; \n";
137 |
138 | return "\n" + MysqlBaseService.SQL_START_PATTERN + "\n" +
139 | safeDeleteSQL + "\n" +
140 | "\n" + MysqlBaseService.SQL_END_PATTERN + "\n";
141 | }
142 |
143 | /**
144 | * This function will extract the database name from the
145 | * supplied JDBC connection URL.
146 | * @param jdbcURL JDBC Connection URL
147 | * @return database name extracted from the connection URL
148 | * @exception MysqlBackup4JException if an invalid jdbcURL is supplied
149 | */
150 | public static String extractDatabaseNameFromJDBCUrl(String jdbcURL) {
151 |
152 | if(jdbcURL == null || jdbcURL.isEmpty())
153 | throw new MysqlBackup4JException("Null or Empty JDBC URL supplied: " + jdbcURL);
154 |
155 | //strip the extra properties from the URL
156 | String jdbcURLWithoutParams;
157 | if(jdbcURL.contains("?")) {
158 | jdbcURLWithoutParams = jdbcURL.substring(0, jdbcURL.indexOf("?"));
159 | }
160 | else {
161 | jdbcURLWithoutParams = jdbcURL;
162 | }
163 |
164 | return jdbcURLWithoutParams.substring(jdbcURLWithoutParams.lastIndexOf("/") + 1);
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/MysqlExportService.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import com.smattme.exceptions.MysqlBackup4JException;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.zeroturnaround.zip.ZipUtil;
7 |
8 | import java.io.File;
9 | import java.io.FileOutputStream;
10 | import java.io.IOException;
11 | import java.sql.*;
12 | import java.text.SimpleDateFormat;
13 | import java.util.Date;
14 | import java.util.List;
15 | import java.util.Objects;
16 | import java.util.Properties;
17 |
18 | import static com.smattme.helpers.MysqlExportServiceHelper.bytesToHex;
19 |
20 | /**
21 | * Created by seun_ on 24-Feb-18.
22 | *
23 | */
24 | public class MysqlExportService {
25 |
26 | private Statement stmt;
27 | private String database;
28 | private String generatedSql = "";
29 | private Logger logger = LoggerFactory.getLogger(getClass());
30 | private final String LOG_PREFIX = "mysql-backup4j-export";
31 | private String dirName = "mysql-backup4j-export-temp";
32 | private String sqlFileName = "";
33 | private String zipFileName = "";
34 | private Properties properties;
35 | private File generatedZipFile;
36 |
37 | public static final String EMAIL_HOST = "EMAIL_HOST";
38 | public static final String EMAIL_PORT = "EMAIL_PORT";
39 | public static final String EMAIL_USERNAME = "EMAIL_USERNAME";
40 | public static final String EMAIL_PASSWORD = "EMAIL_PASSWORD";
41 | public static final String EMAIL_SUBJECT = "EMAIL_SUBJECT";
42 | public static final String EMAIL_MESSAGE = "EMAIL_MESSAGE";
43 | public static final String EMAIL_FROM = "EMAIL_FROM";
44 | public static final String EMAIL_TO = "EMAIL_TO";
45 | public static final String EMAIL_SSL_PROTOCOLS = "EMAIL_SSL_PROTOCOLS";
46 | public static final String EMAIL_START_TLS_ENABLED = "EMAIL_START_TLS_ENABLED";
47 | public static final String EMAIL_SMTP_AUTH_ENABLED = "EMAIL_SMTP_AUTH_ENABLED";
48 | public static final String DB_NAME = "DB_NAME";
49 | public static final String DB_USERNAME = "DB_USERNAME";
50 | public static final String DB_PASSWORD = "DB_PASSWORD";
51 | public static final String DB_HOST = "DB_HOST";
52 | public static final String DB_PORT = "DB_PORT";
53 | public static final String PRESERVE_GENERATED_ZIP = "PRESERVE_GENERATED_ZIP";
54 | public static final String PRESERVE_GENERATED_SQL_FILE = "PRESERVE_GENERATED_SQL_FILE";
55 | public static final String TEMP_DIR = "TEMP_DIR";
56 | public static final String ADD_IF_NOT_EXISTS = "ADD_IF_NOT_EXISTS";
57 |
58 |
59 | /**
60 | * @deprecated
61 | * This is deprecated in favour of the same option available
62 | * in the {@link MysqlImportService} class.
63 | */
64 | public static final String DROP_TABLES = "DROP_TABLES";
65 |
66 |
67 | /**
68 | * @deprecated
69 | * This is deprecated in favour of the same option available
70 | * in the {@link MysqlImportService} class.
71 | */
72 | public static final String DELETE_EXISTING_DATA = "DELETE_EXISTING_DATA";
73 |
74 |
75 | public static final String JDBC_CONNECTION_STRING = "JDBC_CONNECTION_STRING";
76 | public static final String JDBC_DRIVER_NAME = "JDBC_DRIVER_NAME";
77 | public static final String SQL_FILE_NAME = "SQL_FILE_NAME";
78 |
79 |
80 | public MysqlExportService(Properties properties) {
81 | this.properties = properties;
82 | }
83 |
84 | /**
85 | * This function will check if the required minimum
86 | * properties are set for database connection and exporting
87 | * password is excluded here because it's possible to have a mysql database
88 | * user with no password
89 | * @return true if all required properties are present and false if otherwise
90 | */
91 | private boolean isValidateProperties() {
92 | return properties != null &&
93 | properties.containsKey(DB_USERNAME) &&
94 | (properties.containsKey(DB_NAME) || properties.containsKey(JDBC_CONNECTION_STRING));
95 | }
96 |
97 | /**
98 | * This function will check if all the minimum
99 | * required email properties are set,
100 | * that can facilitate sending of exported
101 | * sql to email
102 | * @return bool
103 | */
104 | private boolean isEmailPropertiesSet() {
105 | return properties != null &&
106 | properties.containsKey(EMAIL_HOST) &&
107 | properties.containsKey(EMAIL_PORT) &&
108 | properties.containsKey(EMAIL_USERNAME) &&
109 | properties.containsKey(EMAIL_PASSWORD) &&
110 | properties.containsKey(EMAIL_FROM) &&
111 | properties.containsKey(EMAIL_TO);
112 | }
113 |
114 | /**
115 | * This function will return true
116 | * or false based on the availability
117 | * or absence of a custom output sql
118 | * file name
119 | * @return bool
120 | */
121 | private boolean isSqlFileNamePropertySet(){
122 | return properties != null &&
123 | properties.containsKey(SQL_FILE_NAME);
124 | }
125 |
126 | /**
127 | * This will generate the SQL statement
128 | * for creating the table supplied in the
129 | * method signature
130 | * @param table the table concerned
131 | * @return String
132 | * @throws SQLException exception
133 | */
134 | private String getTableInsertStatement(String table) throws SQLException {
135 |
136 | StringBuilder sql = new StringBuilder();
137 | ResultSet rs;
138 | boolean addIfNotExists = Boolean.parseBoolean(properties.containsKey(ADD_IF_NOT_EXISTS) ? properties.getProperty(ADD_IF_NOT_EXISTS, "true") : "true");
139 |
140 |
141 | if(table != null && !table.isEmpty()){
142 | rs = stmt.executeQuery("SHOW CREATE TABLE " + "`" + table + "`;");
143 | while ( rs.next() ) {
144 | String qtbl = rs.getString(1);
145 | String query = rs.getString(2);
146 | sql.append("\n\n--");
147 | sql.append("\n").append(MysqlBaseService.SQL_START_PATTERN).append(" table dump : ").append(qtbl);
148 | sql.append("\n--\n\n");
149 |
150 | if(addIfNotExists) {
151 | query = query.trim().replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
152 | }
153 |
154 | sql.append(query).append(";\n\n");
155 | }
156 |
157 | sql.append("\n\n--");
158 | sql.append("\n").append(MysqlBaseService.SQL_END_PATTERN).append(" table dump : ").append(table);
159 | sql.append("\n--\n\n");
160 | }
161 |
162 | return sql.toString();
163 | }
164 |
165 | /**
166 | * this will generate the SQL statement to re-create
167 | * the supplied view
168 | * @param view the name of the View
169 | * @return an SQL to create the view
170 | * @throws SQLException on error
171 | */
172 | private String getCreateViewStatement(String view) throws SQLException {
173 |
174 | StringBuilder sql = new StringBuilder();
175 | ResultSet rs;
176 |
177 | if(view != null && !view.isEmpty()) {
178 | rs = stmt.executeQuery("SHOW CREATE VIEW " + "`" + view + "`;");
179 | while ( rs.next() ) {
180 | String viewName = rs.getString(1);
181 | String viewQuery = rs.getString(2);
182 | sql.append("\n\n--");
183 | sql.append("\n").append(MysqlBaseService.SQL_START_PATTERN).append(" view dump : ").append(view);
184 | sql.append("\n--\n\n");
185 |
186 | String finalQuery = "CREATE OR REPLACE VIEW `" + viewName + "` " + (viewQuery.substring(viewQuery.indexOf("AS")).trim());
187 | sql.append(finalQuery).append(";\n\n");
188 | }
189 |
190 | sql.append("\n\n--");
191 | sql.append("\n").append(MysqlBaseService.SQL_END_PATTERN).append(" view dump : ").append(view);
192 | sql.append("\n--\n\n");
193 | }
194 |
195 | return sql.toString();
196 | }
197 |
198 |
199 | /**
200 | * This function will generate the insert statements needed
201 | * to recreate the table under processing.
202 | * @param table the table to get inserts statement for
203 | * @return String generated SQL insert
204 | * @throws SQLException exception
205 | */
206 | private String getDataInsertStatement(String table) throws SQLException {
207 |
208 | StringBuilder sql = new StringBuilder();
209 |
210 | ResultSet rs = stmt.executeQuery("SELECT * FROM " + "`" + table + "`;");
211 |
212 | //move to the last row to get max rows returned
213 | rs.last();
214 | int rowCount = rs.getRow();
215 |
216 | //there are no records just return empty string
217 | if(rowCount <= 0) {
218 | return sql.toString();
219 | }
220 |
221 | sql.append("\n--").append("\n-- Inserts of ").append(table).append("\n--\n\n");
222 |
223 | //temporarily disable foreign key constraint
224 | sql.append("\n/*!40000 ALTER TABLE `").append(table).append("` DISABLE KEYS */;\n");
225 |
226 | sql.append("\n--\n")
227 | .append(MysqlBaseService.SQL_START_PATTERN).append(" table insert : ").append(table)
228 | .append("\n--\n");
229 |
230 | sql.append("INSERT INTO `").append(table).append("`(");
231 |
232 | ResultSetMetaData metaData = rs.getMetaData();
233 | int columnCount = metaData.getColumnCount();
234 |
235 | //generate the column names that are present
236 | //in the returned result set
237 | //at this point the insert is INSERT INTO (`col1`, `col2`, ...)
238 | for(int i = 0; i < columnCount; i++) {
239 | sql.append("`")
240 | .append(metaData.getColumnName( i + 1))
241 | .append("`, ");
242 | }
243 |
244 | //remove the last whitespace and comma
245 | sql.deleteCharAt(sql.length() - 1).deleteCharAt(sql.length() - 1).append(") VALUES \n");
246 |
247 | //now we're going to build the values for data insertion
248 | rs.beforeFirst();
249 | while(rs.next()) {
250 | sql.append("(");
251 | for(int i = 0; i < columnCount; i++) {
252 |
253 | int columnType = metaData.getColumnType(i + 1);
254 | int columnIndex = i + 1;
255 |
256 | //this is the part where the values are processed based on their type
257 | if(Objects.isNull(rs.getObject(columnIndex))) {
258 | sql.append(rs.getObject(columnIndex)).append(", ");
259 | }
260 | else if( columnType == Types.INTEGER || columnType == Types.TINYINT || columnType == Types.BIT
261 | || columnType == Types.SMALLINT || columnType == Types.BIGINT) {
262 | sql.append(rs.getInt(columnIndex)).append(", ");
263 | }
264 | else if(columnType == Types.REAL || columnType == Types.FLOAT || columnType == Types.DOUBLE || columnType == Types.DECIMAL
265 | || columnType == Types.NUMERIC) {
266 | sql.append(rs.getDouble(columnIndex)).append(", ");
267 | }
268 |
269 | else if(columnType == Types.BINARY || columnType == Types.BLOB || columnType == Types.LONGVARBINARY || columnType == Types.VARBINARY) {
270 | sql.append("0x").append(bytesToHex(rs.getBytes(columnIndex))).append(", ");
271 | }
272 | else {
273 |
274 | String val = rs.getString(columnIndex);
275 | //escape the single quotes that might be in the value
276 | val = val.replace("'", "\\'");
277 |
278 | sql.append("'").append(val).append("', ");
279 | }
280 | }
281 |
282 | //now that we're done with a row
283 | //let's remove the last whitespace and comma
284 | sql.deleteCharAt(sql.length() - 1).deleteCharAt(sql.length() - 1);
285 |
286 | //if this is the last row, just append a closing
287 | //parenthesis otherwise append a closing parenthesis and a comma
288 | //for the next set of values
289 | if(rs.isLast()) {
290 | sql.append(")");
291 | } else {
292 | sql.append("),\n");
293 | }
294 | }
295 |
296 | //now that we are done processing the entire row
297 | //let's add the terminator
298 | sql.append(";");
299 |
300 | sql.append("\n--\n")
301 | .append(MysqlBaseService.SQL_END_PATTERN).append(" table insert : ").append(table)
302 | .append("\n--\n");
303 |
304 | //enable FK constraint
305 | sql.append("\n/*!40000 ALTER TABLE `").append(table).append("` ENABLE KEYS */;\n");
306 |
307 | return sql.toString();
308 | }
309 |
310 |
311 | /**
312 | * This is the entry function that'll
313 | * coordinate getTableInsertStatement() and getDataInsertStatement()
314 | * for every table in the database to generate a whole
315 | * script of SQL
316 | * @return String
317 | * @throws SQLException exception
318 | */
319 | private String exportToSql() throws SQLException {
320 |
321 | StringBuilder sql = new StringBuilder();
322 | sql.append("--");
323 | sql.append("\n-- Generated by mysql-backup4j");
324 | sql.append("\n-- https://github.com/SeunMatt/mysql-backup4j");
325 | sql.append("\n-- Date: ").append(new SimpleDateFormat("d-M-Y H:m:s").format(new Date()));
326 | sql.append("\n--");
327 |
328 | //these declarations are extracted from HeidiSQL
329 | sql.append("\n\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;")
330 | .append("\n/*!40101 SET NAMES utf8 */;")
331 | .append("\n/*!50503 SET NAMES utf8mb4 */;")
332 | .append("\n/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;")
333 | .append("\n/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;");
334 |
335 |
336 | //get the tables that are in the database
337 | // List tables = MysqlBaseService.getAllTables(database, stmt);
338 | TablesResponse allTablesAndViews = MysqlBaseService.getAllTablesAndViews(database, stmt);
339 |
340 | List tables = allTablesAndViews.getTables();
341 | //for every table, get the table creation and data
342 | // insert statement
343 | for (String s: tables) {
344 | try {
345 | sql.append(getTableInsertStatement(s.trim()));
346 | sql.append(getDataInsertStatement(s.trim()));
347 | } catch (SQLException e) {
348 | logger.error("Exception occurred while processing table: " + s, e);
349 | }
350 | }
351 |
352 |
353 | //process views if there's any
354 | List views = allTablesAndViews.getViews();
355 | for (String v: views) {
356 | try {
357 | sql.append(getCreateViewStatement(v.trim()));
358 | } catch (SQLException e) {
359 | logger.error("Exception occurred while processing view: " + v, e);
360 | }
361 | }
362 |
363 | sql.append("\n/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;")
364 | .append("\n/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;")
365 | .append("\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;");
366 |
367 | this.generatedSql = sql.toString();
368 | return sql.toString();
369 | }
370 |
371 | /**
372 | * This is the entry point for exporting
373 | * the database. It performs validation and
374 | * the initial object initializations,
375 | * database connection and setup
376 | * before ca
377 | * @throws IOException exception
378 | * @throws SQLException exception
379 | * @throws ClassNotFoundException exception
380 | */
381 | public void export() throws IOException, SQLException, ClassNotFoundException {
382 |
383 | //check if properties is set or not
384 | if(!isValidateProperties()) {
385 | String message = "Invalid config properties: The config properties is missing important parameters: DB_NAME, DB_USERNAME and DB_PASSWORD";
386 | logger.error(message);
387 | throw new MysqlBackup4JException(message);
388 | }
389 |
390 | //connect to the database
391 | database = properties.getProperty(DB_NAME);
392 | String jdbcURL = properties.getProperty(JDBC_CONNECTION_STRING, "");
393 | String driverName = properties.getProperty(JDBC_DRIVER_NAME, "");
394 |
395 | Connection connection;
396 |
397 | if(jdbcURL == null || jdbcURL.isEmpty()) {
398 | connection = MysqlBaseService.connect(
399 | properties.getProperty(DB_USERNAME),
400 | properties.getProperty(DB_PASSWORD),
401 | properties.getProperty(DB_HOST, "localhost"),
402 | properties.getProperty(DB_PORT, "3306"),
403 | database,
404 | driverName);
405 | }
406 | else {
407 | //this prioritizes the value set using the setDatabase() over the one extracted from the connection string
408 | //it will only use the one from the connection string if no value is set using the setDatabase()
409 | if(database == null || database.isEmpty()) {
410 | database = MysqlBaseService.extractDatabaseNameFromJDBCUrl(jdbcURL);
411 | }
412 | connection = MysqlBaseService.connectWithURL(properties.getProperty(DB_USERNAME), properties.getProperty(DB_PASSWORD),
413 | jdbcURL, driverName);
414 | }
415 |
416 | stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
417 |
418 | //generate the final SQL
419 | String sql = exportToSql();
420 |
421 | //close the statement
422 | stmt.close();
423 |
424 | //close the connection
425 | connection.close();
426 |
427 | //create a temp dir to store the exported file for processing
428 | dirName = properties.getProperty(MysqlExportService.TEMP_DIR, dirName);
429 | File file = new File(dirName);
430 | if(!file.exists()) {
431 | boolean res = file.mkdir();
432 | if(!res) {
433 | throw new IOException(LOG_PREFIX + ": Unable to create temp dir: " + file.getAbsolutePath());
434 | }
435 | }
436 |
437 | //write the sql file out
438 | File sqlFolder = new File(dirName + "/sql");
439 | if(!sqlFolder.exists()) {
440 | boolean res = sqlFolder.mkdir();
441 | if(!res) {
442 | throw new IOException(LOG_PREFIX + ": Unable to create temp dir: " + file.getAbsolutePath());
443 | }
444 | }
445 |
446 | sqlFileName = getSqlFilename();
447 | FileOutputStream outputStream = new FileOutputStream( sqlFolder + "/" + sqlFileName);
448 | outputStream.write(sql.getBytes());
449 | outputStream.close();
450 |
451 | //zip the file
452 | zipFileName = dirName + "/" + sqlFileName.replace(".sql", ".zip");
453 | generatedZipFile = new File(zipFileName);
454 | ZipUtil.pack(sqlFolder, generatedZipFile);
455 |
456 | //mail the zipped file if mail settings are available
457 | if(isEmailPropertiesSet()) {
458 | boolean emailSendingRes = EmailService.builder()
459 | .setHost(properties.getProperty(EMAIL_HOST))
460 | .setPort(Integer.parseInt(properties.getProperty(EMAIL_PORT)))
461 | .setToAddress(properties.getProperty(EMAIL_TO))
462 | .setFromAddress(properties.getProperty(EMAIL_FROM))
463 | .setUsername(properties.getProperty(EMAIL_USERNAME))
464 | .setPassword(properties.getProperty(EMAIL_PASSWORD))
465 | .setSslProtocols(properties.getProperty(EMAIL_SSL_PROTOCOLS, "TLSv1.2"))
466 | .setStartTlsEnabled(properties.getProperty(EMAIL_START_TLS_ENABLED, "true"))
467 | .setSmtpAuthEnabled(Boolean.parseBoolean(properties.getProperty(EMAIL_SMTP_AUTH_ENABLED, "true")))
468 | .setSubject(properties.getProperty(EMAIL_SUBJECT, sqlFileName.replace(".sql", "").toUpperCase()))
469 | .setMessage(properties.getProperty(EMAIL_MESSAGE, "Please find attached database backup of " + database))
470 | .setAttachments(new File[]{new File(zipFileName)})
471 | .sendMail();
472 |
473 | if (emailSendingRes) {
474 | logger.debug(LOG_PREFIX + ": Zip File Sent as Attachment to Email Address Successfully");
475 | } else {
476 | logger.error(LOG_PREFIX + ": Unable to send zipped file as attachment to email. See log debug for more info");
477 | }
478 | }
479 |
480 | //clear the generated temp files
481 | clearTempFiles();
482 |
483 | }
484 |
485 | /**
486 | * This function will delete all the
487 | * temp files generated ny the library
488 | * unless it's otherwise instructed not to do
489 | * so by the preserveZipFile variable
490 | *
491 | */
492 | public void clearTempFiles() {
493 |
494 |
495 | if(!Boolean.parseBoolean(properties.getProperty(PRESERVE_GENERATED_SQL_FILE, Boolean.FALSE.toString()))) {
496 | //delete the temp sql file
497 | File sqlFile = new File(dirName + "/sql/" + sqlFileName);
498 | if (sqlFile.exists()) {
499 | boolean res = sqlFile.delete();
500 | logger.debug(LOG_PREFIX + ": " + sqlFile.getAbsolutePath() + " deleted successfully? " + (res ? " TRUE " : " FALSE "));
501 | } else {
502 | logger.debug(LOG_PREFIX + ": " + sqlFile.getAbsolutePath() + " DOES NOT EXIST while clearing Temp Files");
503 | }
504 |
505 | File sqlFolder = new File(dirName + "/sql");
506 | if (sqlFolder.exists()) {
507 | boolean res = sqlFolder.delete();
508 | logger.debug(LOG_PREFIX + ": " + sqlFolder.getAbsolutePath() + " deleted successfully? " + (res ? " TRUE " : " FALSE "));
509 | } else {
510 | logger.debug(LOG_PREFIX + ": " + sqlFolder.getAbsolutePath() + " DOES NOT EXIST while clearing Temp Files");
511 | }
512 |
513 | }
514 |
515 |
516 | //only execute this section if the
517 | //file is not to be preserved
518 | if(!Boolean.parseBoolean(properties.getProperty(PRESERVE_GENERATED_ZIP, Boolean.FALSE.toString()))) {
519 |
520 | //delete the zipFile
521 | File zipFile = new File(zipFileName);
522 | if (zipFile.exists()) {
523 | boolean res = zipFile.delete();
524 | logger.debug(LOG_PREFIX + ": " + zipFile.getAbsolutePath() + " deleted successfully? " + (res ? " TRUE " : " FALSE "));
525 | } else {
526 | logger.debug(LOG_PREFIX + ": " + zipFile.getAbsolutePath() + " DOES NOT EXIST while clearing Temp Files");
527 | }
528 |
529 | //delete the temp folder
530 | File folder = new File(dirName);
531 | if (folder.exists()) {
532 | boolean res = folder.delete();
533 | logger.debug(LOG_PREFIX + ": " + folder.getAbsolutePath() + " deleted successfully? " + (res ? " TRUE " : " FALSE "));
534 | } else {
535 | logger.debug(LOG_PREFIX + ": " + folder.getAbsolutePath() + " DOES NOT EXIST while clearing Temp Files");
536 | }
537 | }
538 |
539 | logger.debug(LOG_PREFIX + ": generated temp files cleared successfully");
540 | }
541 |
542 | /**
543 | * This will get the final output
544 | * sql file name.
545 | * @return String
546 | */
547 | public String getSqlFilename(){
548 | return isSqlFileNamePropertySet() ? properties.getProperty(SQL_FILE_NAME) + ".sql" :
549 | new SimpleDateFormat("d_M_Y_H_mm_ss").format(new Date()) + "_" + database + "_database_dump.sql";
550 | }
551 |
552 | public String getSqlFileName() {
553 | return sqlFileName;
554 | }
555 |
556 | /**
557 | * this is a getter for the raw sql generated in the backup process
558 | * @return generatedSql
559 | */
560 | public String getGeneratedSql() {
561 | return generatedSql;
562 | }
563 |
564 | /**
565 | * this is a getter for the generatedZipFile generatedZipFile File object
566 | * The reference can be used for further processing in
567 | * external systems
568 | * @return generatedZipFile or null
569 | */
570 | public File getGeneratedZipFile() {
571 | if(generatedZipFile != null && generatedZipFile.exists()) {
572 | return generatedZipFile;
573 | }
574 | return null;
575 | }
576 | }
577 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/MysqlImportService.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import com.smattme.exceptions.MysqlBackup4JException;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.sql.Connection;
8 | import java.sql.SQLException;
9 | import java.sql.Statement;
10 | import java.util.ArrayList;
11 | import java.util.Arrays;
12 | import java.util.List;
13 |
14 | /**
15 | * Created by seun_ on 01-Mar-18.
16 | *
17 | */
18 | public class MysqlImportService {
19 |
20 | private String database;
21 | private String username;
22 | private String host = "localhost";
23 | private String port = "3306";
24 | private String password;
25 | private String sqlString;
26 | private String jdbcConnString;
27 | private String jdbcDriver;
28 | private boolean deleteExisting;
29 | private boolean dropExisting;
30 | private List tables;
31 | private Logger logger = LoggerFactory.getLogger(MysqlImportService.class);
32 |
33 | private MysqlImportService() {
34 | this.deleteExisting = false;
35 | this.dropExisting = false;
36 | this.tables = new ArrayList<>();
37 | }
38 |
39 | /**
40 | *
41 | * @return bool
42 | * @throws SQLException exception
43 | * @throws ClassNotFoundException exception
44 | */
45 | public boolean importDatabase() throws SQLException, ClassNotFoundException {
46 |
47 | if(!this.assertValidParams()) {
48 | String message = "Required Parameters not set or empty \n" +
49 | "Ensure database, username, password, sqlString params are configured \n" +
50 | "using their respective setters";
51 | logger.error(message);
52 | throw new MysqlBackup4JException(message);
53 | }
54 |
55 |
56 | //connect to the database
57 | Connection connection;
58 | if(jdbcConnString == null || jdbcConnString.isEmpty()) {
59 | connection = MysqlBaseService.connect(username, password, host, port, database, jdbcDriver);
60 | }
61 | else {
62 |
63 | //this prioritizes the value set using the setDatabase() over the one extracted from the connection string
64 | //it will only use the one from the connection string if no value is set using the setDatabase()
65 | if(database == null || database.isEmpty()) {
66 | database = MysqlBaseService.extractDatabaseNameFromJDBCUrl(jdbcConnString);
67 | logger.debug("database name extracted from connection string: " + database);
68 | }
69 |
70 | connection = MysqlBaseService.connectWithURL(username, password, jdbcConnString, jdbcDriver);
71 | }
72 |
73 | Statement stmt = connection.createStatement();
74 |
75 | if(deleteExisting || dropExisting) {
76 |
77 | //get all the tables, so as to eliminate delete errors due to non-existent tables
78 | TablesResponse allTablesAndViews = MysqlBaseService.getAllTablesAndViews(database, stmt);
79 | tables = allTablesAndViews.getTables();
80 | logger.debug("tables found for deleting/dropping: \n" + tables.toString());
81 |
82 |
83 | //execute delete query for tables
84 | for (String table: tables) {
85 |
86 | //if deleteExisting and dropExisting is true
87 | //skip the deleteExisting query
88 | //dropExisting will take care of both
89 | if(deleteExisting && !dropExisting) {
90 | String delQ = "DELETE FROM " + "`" + table + "`;";
91 | logger.debug("adding " + delQ + " to batch");
92 | stmt.addBatch(delQ);
93 | }
94 |
95 | if(dropExisting) {
96 | String dropQ = "DROP TABLE IF EXISTS " + "`" + table + "`";
97 | logger.debug("adding " + dropQ + " to batch");
98 | stmt.addBatch(dropQ);
99 | }
100 |
101 | }
102 |
103 |
104 | List views = allTablesAndViews.getViews();
105 | //execute delete query for views
106 | for (String view: views) {
107 | if(dropExisting) {
108 | String dropQ = "DROP VIEW IF EXISTS " + "`" + view + "`";
109 | logger.debug("adding " + dropQ + " to batch");
110 | stmt.addBatch(dropQ);
111 | }
112 | }
113 |
114 | }
115 |
116 | //disable foreign key check
117 | stmt.addBatch("SET FOREIGN_KEY_CHECKS = 0");
118 |
119 |
120 | //now process the sql string supplied
121 | while (sqlString.contains(MysqlBaseService.SQL_START_PATTERN)) {
122 |
123 | //get the chunk of the first statement to execute
124 | int startIndex = sqlString.indexOf(MysqlBaseService.SQL_START_PATTERN);
125 | int endIndex = sqlString.indexOf(MysqlBaseService.SQL_END_PATTERN);
126 |
127 | String executable = sqlString.substring(startIndex, endIndex).trim();
128 | logger.debug("adding extracted executable SQL chunk to batch : \n" + executable);
129 | stmt.addBatch(executable);
130 |
131 | //remove the chunk from the whole to reduce it
132 | sqlString = sqlString.substring(endIndex + 1);
133 |
134 | //repeat
135 | }
136 |
137 |
138 | //add enable foreign key check
139 | stmt.addBatch("SET FOREIGN_KEY_CHECKS = 1");
140 |
141 | //now execute the batch
142 | long[] result = stmt.executeLargeBatch();
143 |
144 | if(logger.isDebugEnabled())
145 | logger.debug( result.length + " queries were executed in batches for provided SQL String with the following result : \n" + Arrays.toString(result));
146 |
147 | stmt.close();
148 | connection.close();
149 |
150 | return true;
151 | }
152 |
153 | /**
154 | * This function will check that required parameters
155 | * are set.
156 | * password is excluded here because it's possible to have a mysql database
157 | * user with no password
158 | * @return true if the required params are present and valid, false otherwise
159 | */
160 | private boolean assertValidParams() {
161 | return username != null && !this.username.isEmpty() &&
162 | sqlString != null && !this.sqlString.isEmpty() &&
163 | ( (database != null && !this.database.isEmpty()) || (jdbcConnString != null && !jdbcConnString.isEmpty()) );
164 | }
165 |
166 | /**
167 | * This function will create a new
168 | * MysqlImportService instance thereby facilitating
169 | * a builder pattern
170 | * @return MysqlImportService
171 | */
172 | public static MysqlImportService builder() {
173 | return new MysqlImportService();
174 | }
175 |
176 | public MysqlImportService setDatabase(String database) {
177 | this.database = database;
178 | return this;
179 | }
180 |
181 | public MysqlImportService setUsername(String username) {
182 | this.username = username;
183 | return this;
184 | }
185 |
186 | public MysqlImportService setPassword(String password) {
187 | this.password = password;
188 | return this;
189 | }
190 |
191 | public MysqlImportService setSqlString(String sqlString) {
192 | this.sqlString = sqlString;
193 | return this;
194 | }
195 |
196 | public MysqlImportService setDeleteExisting(boolean deleteExisting) {
197 | this.deleteExisting = deleteExisting;
198 | return this;
199 | }
200 |
201 | public MysqlImportService setDropExisting(boolean dropExistingTable) {
202 | this.dropExisting = dropExistingTable;
203 | return this;
204 | }
205 |
206 | public MysqlImportService setJdbcDriver(String jdbcDriver) {
207 | this.jdbcDriver = jdbcDriver;
208 | return this;
209 | }
210 |
211 | public MysqlImportService setJdbcConnString(String jdbcConnString) {
212 | this.jdbcConnString = jdbcConnString;
213 | return this;
214 | }
215 |
216 | public MysqlImportService setHost(String host) {
217 | this.host = host;
218 | return this;
219 | }
220 |
221 | public MysqlImportService setPort(String port) {
222 | this.port = port;
223 | return this;
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/TablesResponse.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import java.util.List;
4 |
5 | public class TablesResponse {
6 |
7 | private List tables;
8 | private List views;
9 |
10 | public TablesResponse() { }
11 |
12 | public TablesResponse(List tables, List views) {
13 | this.tables = tables;
14 | this.views = views;
15 | }
16 |
17 | public List getTables() {
18 | return tables;
19 | }
20 |
21 | public List getViews() {
22 | return views;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/exceptions/MysqlBackup4JException.java:
--------------------------------------------------------------------------------
1 | package com.smattme.exceptions;
2 |
3 | public class MysqlBackup4JException extends RuntimeException {
4 |
5 | public MysqlBackup4JException(String message) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/smattme/helpers/MysqlExportServiceHelper.java:
--------------------------------------------------------------------------------
1 | package com.smattme.helpers;
2 |
3 | public class MysqlExportServiceHelper {
4 |
5 | public static String bytesToHex(byte[] bytes) {
6 | StringBuilder hexString = new StringBuilder();
7 | for (byte b : bytes) {
8 | String hex = Integer.toHexString(0xff & b);
9 | if (hex.length() == 1) hexString.append('0');
10 | hexString.append(hex);
11 | }
12 | return hexString.toString();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/com/smattme/MysqlBackup4JIntegrationTest.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import org.junit.jupiter.api.AfterAll;
4 | import org.junit.jupiter.api.Assertions;
5 | import org.junit.jupiter.api.BeforeAll;
6 | import org.junit.jupiter.api.Test;
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.testcontainers.containers.GenericContainer;
10 | import org.testcontainers.containers.MySQLContainer;
11 |
12 | import java.io.BufferedReader;
13 | import java.io.File;
14 | import java.io.InputStream;
15 | import java.io.InputStreamReader;
16 | import java.net.URL;
17 | import java.nio.file.Files;
18 | import java.sql.Connection;
19 | import java.sql.ResultSet;
20 | import java.sql.Statement;
21 | import java.util.Objects;
22 | import java.util.Properties;
23 | import java.util.regex.Matcher;
24 | import java.util.regex.Pattern;
25 | import java.util.stream.Collectors;
26 |
27 | import static org.junit.jupiter.api.Assertions.assertNotNull;
28 | import static org.junit.jupiter.api.Assertions.assertTrue;
29 |
30 | /**
31 | * Created by seun_ on 10-Oct-20.
32 | *
33 | */
34 | class MysqlBackup4JIntegrationTest {
35 |
36 | private static final Logger logger = LoggerFactory.getLogger(MysqlBackup4JIntegrationTest.class);
37 | private static final String TEST_DB = "mysqlbackup4j_test";
38 | private static final String RESTORED_DB = "mysqlbackup4j_restored";
39 | private static final String DB_USERNAME = "root";
40 | private static final String DB_PASSWORD = "backup4j";
41 | private static final String DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
42 | protected static String MYSQL_DB_PORT = "3306";
43 | protected static String MYSQL_DB_HOST = "localhost";
44 |
45 | private static final MySQLContainer> mysql = new MySQLContainer<>("mysql:8.1.0");
46 |
47 | @BeforeAll
48 | static void setUp() {
49 | mysql.withUsername(DB_USERNAME)
50 | .withPassword(DB_PASSWORD)
51 | .withExposedPorts(3306)
52 | .withInitScript("mysql_init.sql")
53 | .start();
54 | MYSQL_DB_PORT = mysql.getMappedPort(3306).toString();
55 | MYSQL_DB_HOST = mysql.getHost();
56 | logger.info("MYSQL_DB_HOST: {}, MYSQL_DB_PORT: {}", MYSQL_DB_HOST, MYSQL_DB_PORT);
57 | }
58 |
59 | @AfterAll
60 | static void tearDown() {
61 | if (Objects.nonNull(mysql)) {
62 | mysql.stop();
63 | }
64 | }
65 |
66 |
67 | @Test
68 | void givenDBCredentials_whenExportDatabaseAndImportDatabase_thenBackUpAndRestoreTestDbSuccessfully() throws Exception {
69 |
70 | Properties properties = new Properties();
71 | properties.setProperty(MysqlExportService.DB_NAME, TEST_DB);
72 | properties.setProperty(MysqlExportService.DB_USERNAME, DB_USERNAME);
73 | properties.setProperty(MysqlExportService.DB_PASSWORD, DB_PASSWORD);
74 |
75 | properties.setProperty(MysqlExportService.DB_HOST, MYSQL_DB_HOST);
76 | properties.setProperty(MysqlExportService.DB_PORT, MYSQL_DB_PORT);
77 |
78 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_ZIP, "true");
79 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_SQL_FILE, "true");
80 |
81 | properties.setProperty(MysqlExportService.JDBC_DRIVER_NAME, DRIVER_CLASS_NAME);
82 | properties.setProperty(MysqlExportService.ADD_IF_NOT_EXISTS, "true");
83 |
84 |
85 | properties.setProperty(MysqlExportService.TEMP_DIR, new File("external").getPath());
86 | properties.setProperty(MysqlExportService.SQL_FILE_NAME, "test_output_file_name");
87 |
88 | MysqlExportService mysqlExportService = new MysqlExportService(properties);
89 | mysqlExportService.export();
90 |
91 | String generatedSql = mysqlExportService.getGeneratedSql();
92 | Assertions.assertFalse(generatedSql.isEmpty());
93 | // logger.info("generated SQL: \n" + generatedSql);
94 |
95 | File file = mysqlExportService.getGeneratedZipFile();
96 | assertNotNull(file);
97 | logger.info("Generated Filename: " + file.getAbsolutePath());
98 |
99 | File sqlFile = new File("external/sql/test_output_file_name.sql");
100 | logger.info("SQL File name: " + sqlFile.getAbsolutePath());
101 |
102 | String sql = new String(Files.readAllBytes(sqlFile.toPath()));
103 | MysqlImportService res = MysqlImportService.builder()
104 | .setJdbcDriver("com.mysql.cj.jdbc.Driver")
105 | .setDatabase(RESTORED_DB)
106 | .setSqlString(sql)
107 | .setUsername(DB_USERNAME)
108 | .setPassword(DB_PASSWORD)
109 | .setHost(MYSQL_DB_HOST)
110 | .setPort(MYSQL_DB_PORT)
111 | .setDeleteExisting(true)
112 | .setDropExisting(true);
113 |
114 | assertTrue(res.importDatabase());
115 |
116 | assertDatabaseBackedUp();
117 |
118 | }
119 |
120 |
121 | @Test
122 | void givenDBCredentialsAndEmailConfig_whenExportDatabase_thenBackUpAndMailDbSuccessfully() throws Exception {
123 |
124 | GenericContainer> smtpServerContainer =
125 | new GenericContainer<>("reachfive/fake-smtp-server:0.8.1")
126 | .withExposedPorts(1080, 1025);
127 | smtpServerContainer.start();
128 | int smtpPort = smtpServerContainer.getMappedPort(1025);
129 | int webPort = smtpServerContainer.getMappedPort(1080);
130 | String smtpServerHost = smtpServerContainer.getHost();
131 |
132 |
133 | Properties properties = new Properties();
134 | properties.setProperty(MysqlExportService.DB_NAME, TEST_DB);
135 | properties.setProperty(MysqlExportService.DB_USERNAME, DB_USERNAME);
136 | properties.setProperty(MysqlExportService.DB_PASSWORD, DB_PASSWORD);
137 |
138 | properties.setProperty(MysqlExportService.DB_HOST, MYSQL_DB_HOST);
139 | properties.setProperty(MysqlExportService.DB_PORT, MYSQL_DB_PORT);
140 |
141 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_ZIP, "true");
142 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_SQL_FILE, "true");
143 |
144 | properties.setProperty(MysqlExportService.JDBC_DRIVER_NAME, DRIVER_CLASS_NAME);
145 | properties.setProperty(MysqlExportService.ADD_IF_NOT_EXISTS, "true");
146 |
147 | properties.setProperty(MysqlExportService.TEMP_DIR, new File("external").getPath());
148 | properties.setProperty(MysqlExportService.SQL_FILE_NAME, "test_output_file_name");
149 |
150 | //properties relating to email config
151 | properties.setProperty(MysqlExportService.EMAIL_HOST, smtpServerHost);
152 | properties.setProperty(MysqlExportService.EMAIL_PORT, String.valueOf(smtpPort));
153 | properties.setProperty(MysqlExportService.EMAIL_USERNAME, "username");
154 | properties.setProperty(MysqlExportService.EMAIL_PASSWORD, "password");
155 | properties.setProperty(MysqlExportService.EMAIL_FROM, "test@smattme.com");
156 | properties.setProperty(MysqlExportService.EMAIL_TO, "backup@smattme.com");
157 | properties.setProperty(MysqlExportService.EMAIL_SSL_PROTOCOLS, "TLSv1.2");
158 | properties.setProperty(MysqlExportService.EMAIL_SMTP_AUTH_ENABLED, "true");
159 | properties.setProperty(MysqlExportService.EMAIL_START_TLS_ENABLED, "true");
160 |
161 | MysqlExportService mysqlExportService = new MysqlExportService(properties);
162 | mysqlExportService.export();
163 |
164 |
165 | String url = String.format("http://%s:%s/api/emails?from=test@smattme.com&to=backup@smattme.com",
166 | smtpServerHost, webPort);
167 |
168 | InputStream inputStream = new URL(url).openConnection().getInputStream();
169 | try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
170 | String response = bufferedReader.lines().collect(Collectors.joining());
171 | Pattern pattern = Pattern.compile("(.)*\"messageId\":(?\".+\").*");
172 | Matcher matcher = pattern.matcher(response);
173 | Assertions.assertEquals(2, matcher.groupCount());
174 | Assertions.assertTrue(matcher.matches());
175 | Assertions.assertNotNull(matcher.group("msgId"));
176 | }
177 |
178 |
179 | inputStream.close();
180 | smtpServerContainer.stop();
181 |
182 |
183 | }
184 |
185 |
186 | @Test
187 | void givenJDBCConString_whenExportDatabaseAndImportDatabase_thenBackUpAndRestoreTestDbSuccessfully() throws Exception {
188 |
189 | Properties properties = new Properties();
190 | properties.setProperty(MysqlExportService.DB_USERNAME, DB_USERNAME);
191 | properties.setProperty(MysqlExportService.DB_PASSWORD, DB_PASSWORD);
192 | properties.setProperty(MysqlExportService.DB_NAME, TEST_DB);
193 | String jdbcUrl = String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false",
194 | MYSQL_DB_HOST, MYSQL_DB_PORT, TEST_DB);
195 | properties.setProperty(MysqlExportService.JDBC_CONNECTION_STRING, jdbcUrl);
196 |
197 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_ZIP, "true");
198 | properties.setProperty(MysqlExportService.PRESERVE_GENERATED_SQL_FILE, "true");
199 | properties.setProperty(MysqlExportService.SQL_FILE_NAME, "test_output_file_name");
200 | properties.setProperty(MysqlExportService.ADD_IF_NOT_EXISTS, "true");
201 |
202 | properties.setProperty(MysqlExportService.TEMP_DIR, new File("external").getPath());
203 |
204 | MysqlExportService mysqlExportService = new MysqlExportService(properties);
205 | mysqlExportService.export();
206 |
207 | String generatedSql = mysqlExportService.getGeneratedSql();
208 | // logger.debug("Final Output:\n {}", generatedSql);
209 |
210 | File file = mysqlExportService.getGeneratedZipFile();
211 | assertNotNull(file);
212 | Assertions.assertEquals("test_output_file_name.zip", file.getName());
213 |
214 |
215 | //import
216 | File sqlFile = new File("external/sql/test_output_file_name.sql");
217 |
218 | String sql = new String(Files.readAllBytes(sqlFile.toPath()));
219 | String restoredJdbcUrl = String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false"
220 | + "&serverTimezone=UTC&useSSL=false",
221 | MYSQL_DB_HOST, MYSQL_DB_PORT, RESTORED_DB);
222 | boolean res = MysqlImportService.builder()
223 | .setSqlString(sql)
224 | .setJdbcConnString(restoredJdbcUrl)
225 | .setUsername(DB_USERNAME)
226 | .setPassword(DB_PASSWORD)
227 | .setDatabase(RESTORED_DB)
228 | .setDeleteExisting(true)
229 | .setDropExisting(true)
230 | .importDatabase();
231 |
232 | assertTrue(res);
233 |
234 | assertDatabaseBackedUp();
235 | }
236 |
237 |
238 | private void assertDatabaseBackedUp() throws Exception {
239 | Connection connection = MysqlBaseService.connect(DB_USERNAME, DB_PASSWORD, MYSQL_DB_HOST, MYSQL_DB_PORT,
240 | RESTORED_DB, DRIVER_CLASS_NAME);
241 | Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
242 | statement.execute("SELECT COUNT(1) as total FROM users");
243 | ResultSet resultSet = statement.getResultSet();
244 | resultSet.first();
245 | assertTrue(resultSet.getLong("total") > 0);
246 | }
247 |
248 | }
--------------------------------------------------------------------------------
/src/test/java/com/smattme/MysqlBackup4JUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.smattme;
2 |
3 | import org.junit.jupiter.api.Assertions;
4 | import org.junit.jupiter.api.Test;
5 |
6 | public class MysqlBackup4JUnitTest {
7 |
8 |
9 | @Test
10 | void givenJDBCURL_whenExtractDatabaseNameFromJDBCURL_thenReturnDatabaseName() {
11 | String jdbcURL = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai";
12 | String databaseName = MysqlBaseService.extractDatabaseNameFromJDBCUrl(jdbcURL);
13 | Assertions.assertEquals("test", databaseName);
14 |
15 | jdbcURL = "jdbc:mysql://localhost:3306/backup4j_test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false";
16 | databaseName = MysqlBaseService.extractDatabaseNameFromJDBCUrl(jdbcURL);
17 | Assertions.assertEquals("backup4j_test", databaseName);
18 |
19 | jdbcURL = "jdbc:mysql://localhost:3306/backup4j_test";
20 | databaseName = MysqlBaseService.extractDatabaseNameFromJDBCUrl(jdbcURL);
21 | Assertions.assertEquals("backup4j_test", databaseName);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/resources/simplelogger.properties:
--------------------------------------------------------------------------------
1 | org.slf4j.simpleLogger.log.org.testcontainers=info
2 | org.slf4j.simpleLogger.log.tc.mysql=info
3 | org.slf4j.simpleLogger.log.com.github.dockerjava=warn
4 | org.slf4j.simpleLogger.log.com.smattme=info
5 | org.slf4j.simpleLogger.defaultLogLevel=debug
6 |
--------------------------------------------------------------------------------