starter-data-initialization.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta name="generator" content="Asciidoctor 2.0.15">
  8. <meta name="author" content="pxzxj, pudge.zxj@gmail.com, 2022/06/08">
  9. <title>Spring Boot Starter 数据库初始化</title>
  10. <link rel="stylesheet" href="css/site.css">
  11. <link href="css/custom.css" rel="stylesheet">
  12. <script src="js/setup.js"></script><script defer src="js/site.js"></script>
  13. </head>
  14. <body class="article toc2 toc-left"><div id="banner-container" class="container" role="banner">
  15. <div id="banner" class="contained" role="banner">
  16. <div id="switch-theme">
  17. <input type="checkbox" id="switch-theme-checkbox" />
  18. <label for="switch-theme-checkbox">Dark Theme</label>
  19. </div>
  20. </div>
  21. </div>
  22. <div id="tocbar-container" class="container" role="navigation">
  23. <div id="tocbar" class="contained" role="navigation">
  24. <button id="toggle-toc"></button>
  25. </div>
  26. </div>
  27. <div id="main-container" class="container">
  28. <div id="main" class="contained">
  29. <div id="doc" class="doc">
  30. <div id="header">
  31. <h1>Spring Boot Starter 数据库初始化</h1>
  32. <div class="details">
  33. <span id="author" class="author">pxzxj</span><br>
  34. <span id="author2" class="author">pudge.zxj@gmail.com</span><br>
  35. <span id="author3" class="author">2022/06/08</span><br>
  36. </div>
  37. <div id="toc" class="toc2">
  38. <div id="toctitle">Table of Contents</div>
  39. <span id="back-to-index"><a href="index.html">Back to index</a></span><ul class="sectlevel1">
  40. <li><a href="#_depend_upon_an_initialized_database">1. Depend Upon an Initialized Database</a>
  41. <ul class="sectlevel2">
  42. <li><a href="#_databaseinitializerdetector">1.1. DatabaseInitializerDetector</a>
  43. <ul class="sectlevel3">
  44. <li><a href="#datasourceScriptDatabaseInitializerDetector">1.1.1. DataSourceScriptDatabaseInitializerDetector</a></li>
  45. </ul>
  46. </li>
  47. <li><a href="#_dependsondatabaseinitializationdetector">1.2. DependsOnDatabaseInitializationDetector</a></li>
  48. </ul>
  49. </li>
  50. <li><a href="#dataSourceScriptDatabaseInitializer">2. DataSourceScriptDatabaseInitializer</a></li>
  51. </ul>
  52. </div>
  53. </div>
  54. <div id="content">
  55. <div id="preamble">
  56. <div class="sectionbody">
  57. <div class="paragraph">
  58. <p>使用Spring Boot框架进行开发时通常会将可以复用的功能封装为一个独立的 <code>starter</code>,其他程序只需引入此starter即可使用相应功能。某些功能的starter必须依赖数据库
  59. 中的表才能正常工作,例如 <code>spring-boot-starter-quartz</code> 就需要初始化许多 <code>quartz</code> 相关的表。一种选择是手动执行这些表对应的建表语句,但更理想的方式是由
  60. <code>starter</code> 自动初始化所需的表</p>
  61. </div>
  62. </div>
  63. </div>
  64. <div class="sect1">
  65. <h2 id="_depend_upon_an_initialized_database"><a class="anchor" href="#_depend_upon_an_initialized_database"></a>1. Depend Upon an Initialized Database</h2>
  66. <div class="sectionbody">
  67. <div class="paragraph">
  68. <p>Spring Boot官方文档的 <a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization.dependencies">Depend Upon an Initialized Database</a>
  69. 章节描述了它对数据库初始化的支持。</p>
  70. </div>
  71. <div class="paragraph">
  72. <p>数据库初始化相关的Bean可以分为两类,一类是执行初始化的Bean,负责创建表插入数据等操作,另一类是依赖于初始化后数据库的Bean,
  73. 这些Bean在创建过程中需要执行查询数据库等操作。 Spring Boot使用 <code>DatabaseInitializerDetector</code> 和 <code>DependsOnDatabaseInitializationDetector</code> 两个接口找到这两类Bean,
  74. 找到后只要通过 <code>BeanDefinition#setDependsOn</code> 方法指定后者依赖前者就能保证第二类Bean一定在第一类Bean之后创建</p>
  75. </div>
  76. <div class="sect2">
  77. <h3 id="_databaseinitializerdetector"><a class="anchor" href="#_databaseinitializerdetector"></a>1.1. DatabaseInitializerDetector</h3>
  78. <div class="paragraph">
  79. <p><code>DatabaseInitializerDetector</code> 的定义如下,<code>detect()</code> 方法返回用来初始化数据库的Bean名称,也就是上文第一类Bean的名称,
  80. <code>detectionComplete()</code> 是 <code>detect()</code> 完成的回调方法,<code>getOrder()</code> 控制 <code>DatabaseInitializerDetector</code> 多个实现的执行顺序</p>
  81. </div>
  82. <div class="listingblock">
  83. <div class="content">
  84. <pre class="highlight"><code class="language-java" data-lang="java">public interface DatabaseInitializerDetector extends Ordered {
  85. /**
  86. * Detect beans defined in the given {@code beanFactory} that initialize a
  87. * {@link DataSource}.
  88. * @param beanFactory bean factory to examine
  89. * @return names of the detected {@code DataSource} initializer beans, or an empty set
  90. * if none were detected.
  91. */
  92. Set&lt;String&gt; detect(ConfigurableListableBeanFactory beanFactory);
  93. default void detectionComplete(ConfigurableListableBeanFactory beanFactory,
  94. Set&lt;String&gt; dataSourceInitializerNames) {
  95. }
  96. @Override
  97. default int getOrder() {
  98. return 0;
  99. }
  100. }
  101. </code></pre>
  102. </div>
  103. </div>
  104. <div class="paragraph">
  105. <p><code>DatabaseInitializerDetector</code> 默认包含如下实现,其中 <code>FlywayDatabaseInitializerDetector</code>、<code>FlywayMigrationInitializerDatabaseInitializerDetector</code>、<code>JpaDatabaseInitializerDetector</code>、
  106. <code>LiquibaseDatabaseInitializerDetector</code> 依赖特定框架或组件,通用性不足,而R2DBC目前应用也不广泛,因此本文仅介绍 <code>DataSourceScriptDatabaseInitializerDetector</code></p>
  107. </div>
  108. <div class="ulist">
  109. <ul>
  110. <li>
  111. <p>DataSourceScriptDatabaseInitializerDetector</p>
  112. </li>
  113. <li>
  114. <p>FlywayDatabaseInitializerDetector</p>
  115. </li>
  116. <li>
  117. <p>FlywayMigrationInitializerDatabaseInitializerDetector</p>
  118. </li>
  119. <li>
  120. <p>JpaDatabaseInitializerDetector</p>
  121. </li>
  122. <li>
  123. <p>LiquibaseDatabaseInitializerDetector</p>
  124. </li>
  125. <li>
  126. <p>R2dbcScriptDatabaseInitializerDetector</p>
  127. </li>
  128. </ul>
  129. </div>
  130. <div class="sect3">
  131. <h4 id="datasourceScriptDatabaseInitializerDetector"><a class="anchor" href="#datasourceScriptDatabaseInitializerDetector"></a>1.1.1. DataSourceScriptDatabaseInitializerDetector</h4>
  132. <div class="paragraph">
  133. <p><code>DataSourceScriptDatabaseInitializerDetector</code> 及其父类定义如下,可以看到它会将所有 <code>DataSourceScriptDatabaseInitializer</code> 类型的Bean作为数据库初始化的Bean,
  134. <code>DataSourceScriptDatabaseInitializer</code> 在 <a href="#dataSourceScriptDatabaseInitializer">下文</a>单独介绍</p>
  135. </div>
  136. <div class="listingblock">
  137. <div class="content">
  138. <pre class="highlight"><code class="language-java" data-lang="java">class DataSourceScriptDatabaseInitializerDetector extends AbstractBeansOfTypeDatabaseInitializerDetector {
  139. static final int PRECEDENCE = Ordered.LOWEST_PRECEDENCE - 100;
  140. @Override
  141. protected Set&lt;Class&lt;?&gt;&gt; getDatabaseInitializerBeanTypes() {
  142. return Collections.singleton(DataSourceScriptDatabaseInitializer.class);
  143. }
  144. @Override
  145. public int getOrder() {
  146. return PRECEDENCE;
  147. }
  148. }
  149. public abstract class AbstractBeansOfTypeDatabaseInitializerDetector implements DatabaseInitializerDetector {
  150. @Override
  151. public Set&lt;String&gt; detect(ConfigurableListableBeanFactory beanFactory) {
  152. try {
  153. Set&lt;Class&lt;?&gt;&gt; types = getDatabaseInitializerBeanTypes();
  154. return new BeansOfTypeDetector(types).detect(beanFactory);
  155. }
  156. catch (Throwable ex) {
  157. return Collections.emptySet();
  158. }
  159. }
  160. /**
  161. * Returns the bean types that should be detected as being database initializers.
  162. * @return the database initializer bean types
  163. */
  164. protected abstract Set&lt;Class&lt;?&gt;&gt; getDatabaseInitializerBeanTypes();
  165. }
  166. </code></pre>
  167. </div>
  168. </div>
  169. </div>
  170. </div>
  171. <div class="sect2">
  172. <h3 id="_dependsondatabaseinitializationdetector"><a class="anchor" href="#_dependsondatabaseinitializationdetector"></a>1.2. DependsOnDatabaseInitializationDetector</h3>
  173. <div class="paragraph">
  174. <p><code>DependsOnDatabaseInitializationDetector</code> 的定义如下,它的 <code>detect()</code> 方法返回依赖于初始化后数据库的Bean的名称</p>
  175. </div>
  176. <div class="listingblock">
  177. <div class="content">
  178. <pre class="highlight"><code class="language-java" data-lang="java">public interface DependsOnDatabaseInitializationDetector {
  179. /**
  180. * Detect beans defined in the given {@code beanFactory} that depend on database
  181. * initialization. If no beans are detected, an empty set is returned.
  182. * @param beanFactory bean factory to examine
  183. * @return names of any beans that depend upon database initialization
  184. */
  185. Set&lt;String&gt; detect(ConfigurableListableBeanFactory beanFactory);
  186. }
  187. </code></pre>
  188. </div>
  189. </div>
  190. <div class="paragraph">
  191. <p><code>DependsOnDatabaseInitializationDetector</code> 默认包含如下实现,其中大部分都用于与其他框架整合,而 <code>AnnotationDependsOnDatabaseInitializationDetector</code> 则比较通用,在任意Bean的声明
  192. 上添加 <code>@DependsOnDatabaseInitialization</code> 注解就可以使其成为一个依赖于初始化后数据库的Bean。</p>
  193. </div>
  194. <div class="ulist">
  195. <ul>
  196. <li>
  197. <p>AnnotationDependsOnDatabaseInitializationDetector 返回声明了 <code>@DependsOnDatabaseInitialization</code> 注解的Bean</p>
  198. </li>
  199. <li>
  200. <p>JdbcIndexedSessionRepositoryDependsOnDatabaseInitializationDetector 返回 <code>JdbcIndexedSessionRepository</code> 类型的Bean,用于与 <code>spring-session</code> 整合</p>
  201. </li>
  202. <li>
  203. <p>JobRepositoryDependsOnDatabaseInitializationDetector 返回 <code>JobRepository</code> 类型的Bean,用于与 <code>spring-batch</code> 整合</p>
  204. </li>
  205. <li>
  206. <p>JooqDependsOnDatabaseInitializationDetector 返回 <code>DSLContext</code> 类型的Bean,用于与 <code>JOOQ</code> 整合</p>
  207. </li>
  208. <li>
  209. <p>JpaDependsOnDatabaseInitializationDetector 返回 <code>EntityManagerFactory</code> 和 <code>AbstractEntityManagerFactoryBean</code> 类型的Bean,用于与JPA整合</p>
  210. </li>
  211. <li>
  212. <p>SchedulerDependsOnDatabaseInitializationDetector 返回 <code>Scheduler</code> 和 <code>SchedulerFactoryBean</code> 类型的Bean,用于与Quartz整合</p>
  213. </li>
  214. <li>
  215. <p>SpringJdbcDependsOnDatabaseInitializationDetector 返回 <code>JdbcOperations</code> 和 <code>NamedParameterJdbcOperations</code> 类型的Bean</p>
  216. </li>
  217. </ul>
  218. </div>
  219. </div>
  220. </div>
  221. </div>
  222. <div class="sect1">
  223. <h2 id="dataSourceScriptDatabaseInitializer"><a class="anchor" href="#dataSourceScriptDatabaseInitializer"></a>2. DataSourceScriptDatabaseInitializer</h2>
  224. <div class="sectionbody">
  225. <div class="paragraph">
  226. <p><a href="#datasourceScriptDatabaseInitializerDetector">上文</a>介绍了 <code>DataSourceScriptDatabaseInitializerDetector</code> 会返回所有 <code>DataSourceScriptDatabaseInitializer</code> 类型的Bean,表示这些Bean用于数据库初始化</p>
  227. </div>
  228. <div class="paragraph">
  229. <p><code>DataSourceScriptDatabaseInitializer</code> 用于执行特定脚本初始化数据库,它的构造器信息如下,第一个参数 <code>dataSource</code> 指定需要初始化的数据源,第二个参数 <code>settings</code> 指定脚本信息,包含 schema脚本位置、data脚本位置、是否错误继续、
  230. 分隔符、编码、模式</p>
  231. </div>
  232. <div class="paragraph">
  233. <p><code>DatabaseInitializationMode</code> 是一个枚举,包含 <code>ALWAYS、EMBEDDED、NEVER</code> 三个枚举值,<code>ALWAYS</code> 表示总是执行 <code>schemaLocations</code> 和 <code>dataLocations</code> 声明的脚本, <code>EMBEDDED</code> 表示只有是内存数据库才执行,<code>NEVER</code> 表示不执行。
  234. 该字段的设置要特别注意,如果指定了 <code>ALWAYS</code> 就意味着每一次项目启动都会执行初始化脚本,如果数据源是 <code>MySQL</code> 等非内存数据库重复执行脚本很可能会报错,当然如果数据库支持 <code>CREATE IF NOT EXISTS</code> 语法可能不会报错</p>
  235. </div>
  236. <div class="listingblock">
  237. <div class="content">
  238. <pre class="highlight"><code class="language-java" data-lang="java">public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer {
  239. private final DataSource dataSource;
  240. public DataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) {
  241. super(settings);
  242. this.dataSource = dataSource;
  243. }
  244. //...
  245. }
  246. public class DatabaseInitializationSettings {
  247. private List&lt;String&gt; schemaLocations;
  248. private List&lt;String&gt; dataLocations;
  249. private boolean continueOnError = false;
  250. private String separator = ";";
  251. private Charset encoding;
  252. private DatabaseInitializationMode mode = DatabaseInitializationMode.EMBEDDED;
  253. //getter,setter
  254. }
  255. </code></pre>
  256. </div>
  257. </div>
  258. <div class="paragraph">
  259. <p>综上所述,只要在 <code>starter</code> 中声明 <code>DataSourceScriptDatabaseInitializer</code> 类型的Bean就能实现数据库的初始化,如果 <code>starter</code> 本身包含了配置类的话,还可以在其配置类中声明 <code>DatabaseInitializationSettings</code> 相关的属性,
  260. 最终实现在 <code>application.yml</code> 中配置初始化脚本位置或者初始化模式等属性。</p>
  261. </div>
  262. </div>
  263. </div>
  264. </div>
  265. <div id="footer">
  266. <div id="footer-text">
  267. Last updated 2024-03-18 05:44:42 UTC
  268. </div>
  269. </div>
  270. <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.3/highlight.min.js"></script>
  271. <script>
  272. if (!hljs.initHighlighting.called) {
  273. hljs.initHighlighting.called = true
  274. ;[].slice.call(document.querySelectorAll('pre.highlight > code')).forEach(function (el) { hljs.highlightBlock(el) })
  275. }
  276. </script>
  277. <script src="https://utteranc.es/client.js"
  278. repo="pxzxj/articles"
  279. issue-term="title"
  280. label="utteranc"
  281. theme="github-light"
  282. crossorigin="anonymous"
  283. async>
  284. </script>
  285. </div>
  286. </div>
  287. </div>
  288. </body>
  289. </html>