├── .github ├── CODEOWNERS ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── attach-artifact-release.yml │ ├── build-nightly.yml │ ├── codeql.yml │ ├── create-release.yml │ ├── dry-run-release.yml │ ├── label-pr.yml │ ├── release-published.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── RELEASE.md ├── pom.xml ├── pom.xml.releaseBackup ├── release.properties └── src ├── main ├── java │ └── liquibase │ │ └── ext │ │ └── hibernate │ │ ├── customfactory │ │ └── CustomMetadataFactory.java │ │ ├── database │ │ ├── HibernateClassicDatabase.java │ │ ├── HibernateDatabase.java │ │ ├── HibernateEjb3Database.java │ │ ├── HibernateGenericDialect.java │ │ ├── HibernateSpringBeanDatabase.java │ │ ├── HibernateSpringPackageDatabase.java │ │ ├── JpaPersistenceDatabase.java │ │ ├── NoOpConnectionProvider.java │ │ ├── NoOpMultiTenantConnectionProvider.java │ │ └── connection │ │ │ ├── HibernateConnection.java │ │ │ ├── HibernateConnectionMetadata.java │ │ │ └── HibernateDriver.java │ │ ├── diff │ │ ├── ChangedColumnChangeGenerator.java │ │ ├── ChangedForeignKeyChangeGenerator.java │ │ ├── ChangedPrimaryKeyChangeGenerator.java │ │ ├── ChangedSequenceChangeGenerator.java │ │ ├── ChangedUniqueConstraintChangeGenerator.java │ │ ├── MissingSequenceChangeGenerator.java │ │ └── UnexpectedIndexChangeGenerator.java │ │ └── snapshot │ │ ├── CatalogSnapshotGenerator.java │ │ ├── ColumnSnapshotGenerator.java │ │ ├── ForeignKeySnapshotGenerator.java │ │ ├── HibernateSnapshotGenerator.java │ │ ├── IndexSnapshotGenerator.java │ │ ├── PrimaryKeySnapshotGenerator.java │ │ ├── SchemaSnapshotGenerator.java │ │ ├── SequenceSnapshotGenerator.java │ │ ├── TableSnapshotGenerator.java │ │ ├── UniqueConstraintSnapshotGenerator.java │ │ ├── ViewSnapshotGenerator.java │ │ └── extension │ │ ├── ExtendedSnapshotGenerator.java │ │ └── TableGeneratorSnapshotGenerator.java ├── resources-filtered │ └── build.properties └── resources │ └── META-INF │ └── services │ ├── liquibase.database.Database │ ├── liquibase.diff.output.changelog.ChangeGenerator │ └── liquibase.snapshot.SnapshotGenerator └── test ├── groovy └── HibernateDiffCommandTest.groovy ├── java ├── com │ └── example │ │ ├── customconfig │ │ └── auction │ │ │ └── Item.java │ │ ├── ejb3 │ │ └── auction │ │ │ ├── AuctionInfo.java │ │ │ ├── AuctionItem.java │ │ │ ├── AuditedItem.java │ │ │ ├── Bid.java │ │ │ ├── BuyNow.java │ │ │ ├── FirstTable.java │ │ │ ├── Item.java │ │ │ ├── Name.java │ │ │ ├── Persistent.java │ │ │ ├── SecondTable.java │ │ │ ├── User.java │ │ │ └── Watcher.java │ │ ├── pojo │ │ └── auction │ │ │ ├── AuctionInfo.java │ │ │ ├── AuctionItem.java │ │ │ ├── Bid.java │ │ │ ├── BuyNow.java │ │ │ ├── Name.java │ │ │ ├── Persistent.java │ │ │ ├── User.java │ │ │ └── Watcher.java │ │ └── timezone │ │ └── Item.java └── liquibase │ ├── ext │ └── hibernate │ │ ├── HibernateIntegrationTest.java │ │ ├── SpringPackageScanningIntegrationTest.java │ │ ├── database │ │ ├── HibernateClassicDatabaseTest.java │ │ ├── HibernateDatabaseTest.java │ │ ├── HibernateEjb3DatabaseTest.java │ │ ├── HibernateSpringDatabaseTest.java │ │ ├── JPAPersistenceDatabaseTest.java │ │ └── connection │ │ │ └── HibernateConnectionTest.java │ │ └── snapshot │ │ ├── ColumnSnapshotGeneratorTest.java │ │ └── TimezoneSnapshotTest.java │ └── harness │ └── diff │ ├── Authors.java │ └── Posts.java └── resources ├── META-INF └── persistence.xml ├── com └── example │ └── pojo │ ├── Hibernate.cfg.xml │ └── auction │ ├── AuctionItem.hbm.xml │ ├── Bid.hbm.xml │ └── User.hbm.xml ├── harness-config.yml ├── liquibase └── harness │ └── diff │ ├── diffDatabases.yml │ └── xml │ ├── Authors.hbm.xml │ ├── Hibernate.cfg.xml │ └── Posts.hbm.xml └── spring.ctx.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @filipelautert 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - "sdou" 10 | 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | 2 | name-template: 'Support for Liquibase Hibernate Extension v$RESOLVED_VERSION' 3 | tag-template: 'v$RESOLVED_VERSION' 4 | exclude-labels: 5 | - 'skipReleaseNotes' 6 | categories: 7 | - title: ':green_book: Notable Changes' 8 | labels: 9 | - 'notableChanges' 10 | - title: '🚀 New Features' 11 | labels: 12 | - 'TypeEnhancement' 13 | - 'TypeTest' 14 | - title: '🐛 Bug Fixes 🛠' 15 | labels: 16 | - 'TypeBug' 17 | - title: '💥 Breaking Changes' 18 | labels: 19 | - 'breakingChanges' 20 | - title: '🤖 Security Driver and Other Updates' 21 | collapse-after: 5 22 | labels: 23 | - 'sdou' 24 | - 'dependencies' 25 | - title: '👏 New Contributors' 26 | labels: 27 | - 'newContributors' 28 | 29 | 30 | change-template: '- (#$NUMBER) $TITLE @$AUTHOR ' 31 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 32 | version-resolver: 33 | major: 34 | labels: 35 | - 'major' 36 | minor: 37 | labels: 38 | - 'minor' 39 | patch: 40 | labels: 41 | - 'feature' 42 | - 'enhancement' 43 | - 'patch' 44 | - 'bugfix' 45 | - 'sdou' 46 | default: patch 47 | template: | 48 | ## Changes 49 | 50 | $CHANGES 51 | 52 | **Full Changelog**: https://github.com/liquibase/liquibase-hibernate/compare/$PREVIOUS_TAG...$RESOLVED_VERSION 53 | -------------------------------------------------------------------------------- /.github/workflows/attach-artifact-release.yml: -------------------------------------------------------------------------------- 1 | name: Attach Artifact to Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: write 13 | actions: read 14 | packages: write 15 | 16 | jobs: 17 | attach-artifact-to-release: 18 | uses: liquibase/build-logic/.github/workflows/extension-attach-artifact-release.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/build-nightly.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build the extension against the latest Liquibase artifact 2 | name: "Nightly build" 3 | 4 | on: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: '0 7 * * 1-5' 8 | 9 | jobs: 10 | nightly-build: 11 | uses: liquibase/build-logic/.github/workflows/os-extension-test.yml@main 12 | with: 13 | nightly: true 14 | java: '[17, 21]' 15 | os: '["ubuntu-latest"]' 16 | secrets: inherit 17 | 18 | hibernate-test: 19 | name: Test Hibernate ${{ matrix.hibernate }} 20 | needs: nightly-build 21 | runs-on: ubuntu-latest 22 | 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | hibernate: [ "6.2.7.Final", "6.3.1.Final" ] 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Set up JDK 32 | uses: actions/setup-java@v4 33 | with: 34 | java-version: 17 35 | distribution: 'temurin' 36 | cache: 'maven' 37 | 38 | - name: Run Compatibility Tests 39 | run: mvn -B jacoco:prepare-agent surefire:test -Dhibernate.version=${{ matrix.hibernate }} 40 | 41 | - name: Run Tests 42 | run: mvn -B jacoco:prepare-agent surefire:test 43 | 44 | - name: Archive Test Results 45 | if: ${{ always() }} 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: test-reports-hibernate-${{ matrix.hibernate }} 49 | path: | 50 | **/target/surefire-reports 51 | **/target/jacoco.exec -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: CodeQL 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: ["main"] 7 | pull_request: 8 | # The branches below must be a subset of the branches above 9 | branches: ["main"] 10 | schedule: 11 | - cron: "16 14 * * 4" 12 | 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | packages: read 18 | 19 | jobs: 20 | codeql: 21 | uses: liquibase/build-logic/.github/workflows/codeql.yml@main 22 | secrets: inherit 23 | with: 24 | languages: '["java"]' 25 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: read 12 | issues: read 13 | statuses: read 14 | actions: read 15 | security-events: write 16 | 17 | jobs: 18 | create-release: 19 | uses: liquibase/build-logic/.github/workflows/create-release.yml@main 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /.github/workflows/dry-run-release.yml: -------------------------------------------------------------------------------- 1 | name: Dry run release 2 | 3 | on: 4 | workflow_dispatch: # Trigger on demand 5 | schedule: # Trigger weekly all Wednesdays at midnight UTC 6 | # Trigger weekly on Wednesday at midnight Austin time (Standard Time) 7 | - cron: "0 6 * * 3" 8 | 9 | permissions: 10 | contents: write 11 | actions: read 12 | packages: write 13 | pull-requests: write 14 | 15 | jobs: 16 | dry-run-attach-artifact-to-release: 17 | uses: liquibase/build-logic/.github/workflows/extension-attach-artifact-release.yml@main 18 | secrets: inherit 19 | with: 20 | dry_run: true 21 | dry_run_version: "0.0.${{ github.run_number }}" 22 | 23 | dry-run-get-draft-release: 24 | needs: dry-run-attach-artifact-to-release 25 | runs-on: ubuntu-latest 26 | outputs: 27 | dry_run_release_id: ${{ steps.get_draft_release_id.outputs.release_id }} 28 | steps: 29 | - name: Get GitHub App token 30 | id: get-token 31 | uses: actions/create-github-app-token@v2 32 | with: 33 | app-id: ${{ secrets.LIQUIBASE_GITHUB_APP_ID }} 34 | private-key: ${{ secrets.LIQUIBASE_GITHUB_APP_PRIVATE_KEY }} 35 | owner: ${{ github.repository_owner }} 36 | permission-contents: read 37 | 38 | - name: Get Draft Release ID 39 | id: get_draft_release_id 40 | run: | 41 | release_name="v0.0.${{ github.run_number }}" 42 | response=$(curl -s -H "Authorization: token ${{ steps.get-token.outputs.token }}" \ 43 | -H "Accept: application/vnd.github.v3+json" \ 44 | "https://api.github.com/repos/${{ github.repository }}/releases") 45 | draft_release=$(echo "$response" | jq -r --arg name "$release_name" '.[] | select(.name == $name and .draft == true)') 46 | if [ -z "$draft_release" ]; then 47 | echo "No draft release found with the name '$release_name'" 48 | exit 1 49 | else 50 | echo "$draft_release" | jq . 51 | release_id=$(echo "$draft_release" | jq -r '.id') 52 | echo "release_id=$release_id" >> $GITHUB_OUTPUT 53 | fi 54 | 55 | dry-run-release-published: 56 | needs: dry-run-get-draft-release 57 | uses: liquibase/build-logic/.github/workflows/extension-release-published.yml@main 58 | secrets: inherit 59 | with: 60 | dry_run: true 61 | dry_run_version: "0.0.${{ github.run_number }}" 62 | dry_run_release_id: ${{ needs.dry-run-get-draft-release.outputs.dry_run_release_id }} 63 | deployToMavenCentral: false 64 | 65 | cleanup: 66 | runs-on: ubuntu-latest 67 | if: always() 68 | needs: [dry-run-get-draft-release, dry-run-release-published] 69 | permissions: 70 | contents: write 71 | env: 72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 73 | steps: 74 | - name: Checkout liquibase 75 | uses: actions/checkout@v4 76 | 77 | - name: Get GitHub App token 78 | id: get-token 79 | uses: actions/create-github-app-token@v2 80 | with: 81 | app-id: ${{ secrets.LIQUIBASE_GITHUB_APP_ID }} 82 | private-key: ${{ secrets.LIQUIBASE_GITHUB_APP_PRIVATE_KEY }} 83 | owner: ${{ github.repository_owner }} 84 | permission-contents: read 85 | 86 | - name: Set up Git 87 | run: | 88 | git config user.name "liquibot" 89 | git config user.email "liquibot@liquibase.org" 90 | 91 | - name: Delete liquibase dry-run tag 92 | if: always() 93 | run: | 94 | git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} 95 | git push origin --delete refs/tags/v0.0.${{ github.run_number }} 96 | echo "Remote tag v0.0.${{ github.run_number }} deleted" 97 | 98 | - name: Delete the dry-run draft release 99 | if: always() 100 | run: | 101 | curl -X DELETE -H "Authorization: token ${{ steps.get-token.outputs.token }}" \ 102 | -H "Accept: application/vnd.github.v3+json" \ 103 | "https://api.github.com/repos/${{ github.repository }}/releases/${{ needs.dry-run-get-draft-release.outputs.dry_run_release_id }}" 104 | 105 | notify: 106 | if: failure() 107 | runs-on: ubuntu-latest 108 | needs: 109 | [ 110 | dry-run-attach-artifact-to-release, 111 | dry-run-get-draft-release, 112 | dry-run-release-published, 113 | cleanup, 114 | ] 115 | steps: 116 | - name: Notify Slack on Build Failure 117 | uses: rtCamp/action-slack-notify@v2 118 | env: 119 | SLACK_COLOR: failure 120 | SLACK_MESSAGE: "View details on GitHub Actions: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} <@U040C8J8143> <@U04P39MS2SW> <@UHHJ6UAEQ> <@U042HRTL4DT>" # Jandro, Sailee, Jake, Filipe 121 | SLACK_TITLE: "❌ ${{ github.repository }} ❌ Build failed on branch ${{ github.ref }} for commit ${{ github.sha }} in repository ${{github.repository}}" 122 | SLACK_USERNAME: liquibot 123 | SLACK_WEBHOOK: ${{ secrets.DRY_RUN_RELEASE_SLACK_WEBHOOK }} 124 | SLACK_ICON_EMOJI: ":robot_face:" 125 | SLACK_FOOTER: "${{ github.repository }}" 126 | SLACK_LINK_NAMES: true 127 | -------------------------------------------------------------------------------- /.github/workflows/label-pr.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize, reopened] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - name: Validate PR Labels 13 | uses: mheap/github-action-required-labels@v5 14 | with: 15 | mode: minimum 16 | count: 1 17 | labels: "breakingChanges, newContributors, notableChanges, sdou, skipReleaseNotes, TypeBug, TypeEnhancement, TypeTest" 18 | add_comment: true 19 | message: "Label error: This PR is being prevented from merging because you have not added one of the labels: {{ provided }}. You'll need to add it before this PR can be merged." -------------------------------------------------------------------------------- /.github/workflows/release-published.yml: -------------------------------------------------------------------------------- 1 | name: Release Extension to Sonatype 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | packages: write 12 | id-token: write 13 | 14 | jobs: 15 | release: 16 | uses: liquibase/build-logic/.github/workflows/extension-release-published.yml@main 17 | secrets: inherit 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request_target: 6 | types: 7 | - opened 8 | - reopened 9 | - synchronize 10 | 11 | jobs: 12 | 13 | authorize: 14 | environment: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} 15 | runs-on: ubuntu-latest 16 | steps: 17 | - run: "true" 18 | 19 | build-test: 20 | needs: authorize 21 | uses: liquibase/build-logic/.github/workflows/os-extension-test.yml@main 22 | secrets: inherit 23 | with: 24 | java: '[17, 21]' 25 | os: '["ubuntu-latest"]' 26 | 27 | hibernate-test: 28 | name: Test Hibernate ${{ matrix.hibernate }} 29 | needs: build-test 30 | runs-on: ubuntu-latest 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | hibernate: [ "6.2.7.Final", "6.3.1.Final", "6.6.1.Final" ] 36 | 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Set up JDK 41 | uses: actions/setup-java@v4 42 | with: 43 | java-version: 17 44 | distribution: 'temurin' 45 | cache: 'maven' 46 | 47 | - name: Run Compatibility Tests 48 | run: mvn -B clean test -Dhibernate.version=${{ matrix.hibernate }} verify 49 | 50 | - name: Run Tests 51 | run: mvn -B clean test verify 52 | 53 | - name: Archive Test Results 54 | if: ${{ always() }} 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: test-reports-hibernate-${{ matrix.hibernate }} 58 | path: | 59 | **/target/surefire-reports 60 | **/target/jacoco.exec 61 | dependabot: 62 | needs: hibernate-test 63 | uses: liquibase/build-logic/.github/workflows/dependabot-automerge.yml@main 64 | secrets: inherit -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .classpath 3 | .project 4 | /.settings 5 | /.idea 6 | *.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Liquibase Hibernate Integration [![Build and Test Extension](https://github.com/liquibase/liquibase-hibernate/actions/workflows/build-nightly.yml/badge.svg)](https://github.com/liquibase/liquibase-hibernate/actions/workflows/build.yml) 2 | 3 | This is a Liquibase extension for connecting to Hibernate. The extension lets you use your Hibernate configuration as a comparison database for diff, diffChangeLog, and generateChangeLog in Liquibase. 4 | 5 | ## Configuring the extension 6 | 7 | These instructions will help you get the extension up and running on your local machine for development and testing purposes. This extension has a prerequisite of Liquibase core in order to use it. Liquibase core can be found at https://www.liquibase.org/download. 8 | 9 | ### Compatibility 10 | 11 | The Liquibase Hibernate extension requires Liquibase 4.x and Java 1.8+. Use `liquibase-hibernate5.jar` or `liquibase-hibernate6.jar` depending on your Hibernate version. 12 | Ideally the extension version should be the same one as Liquibase version. 13 | 14 | This extension can be used with any method of running Liquibase (Command line, Gradle, Maven, Ant, and others.) 15 | 16 | ### Liquibase CLI 17 | 18 | Download [the latest released Liquibase extension](https://github.com/liquibase/liquibase-hibernate/releases) `.jar` file and place it in the `liquibase/lib` install directory. If you want to use another location, specify the extension `.jar` file in the `classpath` of your [liquibase.properties file](https://docs.liquibase.com/workflows/liquibase-community/creating-config-properties.html). 19 | 20 | ## Maven 21 | 22 | This extension is available in the maven repository under group __org.liquibase.ext__, artifacts: 23 | 24 | * __liquibase-hibernate6__ Hibernate 6.0+ support 25 | * __liquibase-hibernate5__ Hibernate 5.0+ support 26 | 27 | Specify the Liquibase extension in the `` section of your POM file by adding the `org.liquibase.ext` dependency for the Liquibase plugin. 28 | 29 | ``` 30 | 31 | 33 | org.liquibase 34 | liquibase-maven-plugin 35 | 4.19.0 36 | 37 | 39 | liquibase.properties 40 | 41 | 42 | 44 | 45 | org.liquibase.ext 46 | liquibase-hibernate 47 | ${liquibase-hibernate.version} 48 | 49 | 50 | 51 | ``` 52 | 53 | ## Contribution 54 | 55 | To file a bug, improve documentation, or contribute code, follow our [guidelines for contributing](https://www.liquibase.org/community). 56 | 57 | [This step-by-step instructions](https://www.liquibase.org/community/contribute/code) will help you contribute code for the extension. 58 | 59 | Once you have created a PR for this extension you can find the artifact for your build using the following link: [https://github.com/liquibase/liquibase-hibernate/actions/workflows/build.yml](https://github.com/liquibase/liquibase-hibernate/actions/workflows/build.yml). 60 | 61 | ## Hibernate 5 vs. Hibernate 6 62 | 63 | The master branch is compatible with Hibernate 6+. 64 | The `hibernate5` branch is compatible with Hibernate 5.6+ 65 | 66 | Ideally changes should go into the `hibernate5` branch and then be merged into master in order to support Hibernate 5 and 6. 67 | 68 | ## Documentation 69 | 70 | [Using Liquibase with Hibernate](https://docs.liquibase.com/workflows/database-setup-tutorials/hibernate.html) 71 | 72 | ## More Information 73 | 74 | For more information, see the [project wiki](https://github.com/liquibase/liquibase-hibernate/wiki/). 75 | 76 | ## Issue Tracking 77 | 78 | Any issues can be logged in the [Github issue tracker](https://github.com/liquibase/liquibase-hibernate/issues). 79 | 80 | ## License 81 | 82 | This project is licensed under the [Apache License Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.html). 83 | 84 | ## Using Liquibase Test Harness' Diff test 85 | Liquibase's Hibernate extension uses [Liquibase Test Harness](https://github.com/liquibase/liquibase-test-harness) for integration testing. 86 | 87 | 88 | The `HibernateDiffCommandTest` class extends the `DiffCommandTest` class from Test Harness and utilizes the ability to check differences between two databases. 89 | As Hibernate is not a Relational database, this is our method of checking that database objects generated/updated by the Liquibase Hibernate extension against a database have the correct attributes. 90 | 91 | In general the `DiffCommandTest` works by utilizing the Liquibase diff command to check 92 | differences between two databases, then it creates a changelog file based on diff, then it applies these changes to the target database and checks the diff again. 93 | There still could be some differences afterwards as different DBs support different features, so while checking diffs again the test will ignore diffs that are expected. 94 | 95 | ### Configurations for this test are hosted in 2 files: 96 | * `src/test/resources/harness-config.yml` -- this is a general config file for Test Harness where DB connection details are specified. 97 | * `src/test/resources/liquibase/harness/diff/diffDatabases.yml` -- this file specifies which DBs should be compared and what the diffs are expected even after we try to bring the target DB to same state as the reference DB. 98 | 99 | The `DiffCommandTest` will take all pairs of targetDB-referenceDB from `diffDatabases.yml` . The test also takes the `*.cfg.xml` configuration files into consideration. And then the paths to these config files work as the DB connection URLs in the `harness-config.yml` file. 100 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Workflow 2 | The release automation is designed to quickly release updates to liquibase extensions. This routinely happens when there is an update to liquibase core. There are unique automates automated steps when a pull requests is created by dependabot for a `Bump liquibase-core from *.*.* to *.*.*`, but these steps can also be taken manually for a patch or other manual release. 3 | 4 | ## Triggers 5 | ### Pull Request Opened 6 | When all pull requests are opened the Unit Tests will run and they must pass before the PR can be merged. For a liquibase core bump PR, the application version in the POM will automatically be set to match the liquibase core version. If creating a manual PR for release, the `*.*.*` tag in the POM will need to be set to the correct version without the `SNAPSHOT` suffix in order to release to Sonatype Nexus. For example, `4.3.5.1/version>` to release a patch version for the extension release for liquibase core 4.3.5. 7 | ### Pull Request Labeled as Release Candidate 8 | If the `Extension Release Candidate :rocket:` label is applied to the PR, this is the trigger for GitHub Actions to run the full Integration Test suite matrix on the pull requests because this commit will become the next release. For a liquibase core bump, this label will automatically be applied to the dependabot PR. If this is a manual release, manually applying the label will also start the release testing and subsequent automation. 9 | ### Pull Request is Approved and Merged to Main 10 | If a Pull Request is merged into main and is labeled as release candidate the following automation steps will be taken: 11 | * Signed artifact is built 12 | * A draft GitHub Release is created proper tagging, version name, and artifact 13 | * The application version in the POM is bumped to be the next SNAPSHOT version for development 14 | ### Draft Release is Published 15 | Once the GitHub release is published, the signed artifact is uploaded to Sonatype Nexus. The `true` option is defined in the POM, so for all releases without the `SNAPSHOT` suffix, they will automatically release after all the staging test have passed. If everything goes well, no further manual action is required. 16 | 17 | ## Testing 18 | The workflow separates Unit Test from Integration Tests and runs them at separate times, as mentioned above. In order to separate the tests, they must be in separate files. Put all Unit Tests into files that end with `Test.java` and Integration Test files should end with `IT.java`. For example the tests for the Liquibase Postgresql Extension now look like: 19 | ``` 20 | > src 21 | > test 22 | > java 23 | > liquibase.ext 24 | > copy 25 | CopyChangeIT.java 26 | CopyChangeTest.java 27 | > vacuum 28 | VacuumChangeTest.java 29 | ``` 30 | Any tests that require a JDBC connection to a running database are integration tests and should be in the `IT.java` files. 31 | 32 | ## Repository Configuration 33 | ### GPG SECRET 34 | Github secret named: `GPG_SECRET` 35 | 36 | According to [the advanced java setup docs for github actions](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg) the GPG key should be exported by: `gpg --armor --export-secret-keys YOUR_ID`. From the datical/build-maven:jdk-8 docker container, this can be export by the following: 37 | ```bash 38 | $ docker run -it -u root docker.artifactory.datical.net/datical/build-maven:jdk-8 bash 39 | 40 | $ gpg -k 41 | /home/jenkins/.gnupg/pubring.kbx 42 | -------------------------------- 43 | pub rsa2048 2020-02-12 [SC] [expires: 2022-02-11] 44 | **** OBFUSCATED ID **** 45 | uid [ultimate] Liquibase 46 | sub rsa2048 2020-02-12 [E] [expires: 2022-02-11] 47 | 48 | $ gpg --armor --export-secret-keys --pinentry-mode loopback **** OBFUSCATED ID **** 49 | Enter passphrase: *** GPG PASSPHRASE *** 50 | -----BEGIN PGP PRIVATE KEY BLOCK----- 51 | ****** 52 | ****** 53 | =XCvo 54 | -----END PGP PRIVATE KEY BLOCK----- 55 | ``` 56 | 57 | ### GPG PASSPHRASE 58 | Github secret named: `GPG_PASSPHRASE` 59 | The passphrase is the same one used previously for the manual release and is documented elsewhere for the manual release process. 60 | 61 | ### SONATYPE USERNAME 62 | Github secret named: `SONATYPE_USERNAME` 63 | 64 | The username or token for the sonatype account. Current managed and shared via lastpass for the Shared-DevOps group. 65 | 66 | ### SONATYPE TOKEN 67 | Github secret named: `SONATYPE_TOKEN` 68 | 69 | The password or token for the sonatype account. Current managed and shared via lastpass for the Shared-DevOps group. 70 | 71 | ### Label Settings 72 | Create a label with the following settings: 73 | * Label name: `Extension Release Candidate :rocket:` 74 | * Description: `Release Candidate for Extension` 75 | * Color: `#ff3d00` 76 | 77 | ## Useful Links 78 | * [Advanced Java Setup for GitHub Actions](https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#gpg) 79 | * [Deploying to Sonatype Nexus with Apache Maven](https://central.sonatype.org/publish/publish-maven/) 80 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.liquibase 7 | liquibase-parent-pom 8 | 0.5.8 9 | 10 | 11 | org.liquibase.ext 12 | liquibase-hibernate6 13 | 4.33.0-SNAPSHOT 14 | 15 | Liquibase Hibernate Integration 16 | Liquibase extension for hibernate integration including generating changesets based on changed 17 | hibernate mapping files 18 | 19 | https://github.com/liquibase/liquibase-hibernate/wiki 20 | 21 | 22 | Liquibase.org 23 | http://www.liquibase.org 24 | 25 | 26 | 27 | 28 | nvoxland 29 | Nathan Voxland 30 | nathan.voxland@liquibase.org 31 | 32 | architect 33 | developer 34 | 35 | -6 36 | 37 | 38 | randomeizer 39 | David Peterson 40 | david.peterson@servicerocket.com 41 | http://www.servicerocket.com/ 42 | 43 | developer 44 | 45 | 46 | 47 | fxbonnet 48 | Francois-Xavier Bonnet 49 | francois-xavier.bonnet@centraliens.net 50 | http://www.octo.com/ 51 | 52 | architect 53 | developer 54 | 55 | 56 | 57 | 58 | 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | Apache License, Version 2.0 62 | 63 | 64 | 65 | 66 | https://github.com/liquibase/liquibase-hibernate/issues 67 | 68 | 69 | 70 | https://github.com/liquibase/liquibase-hibernate/actions 71 | 72 | 73 | 74 | 6.6.15.Final 75 | 6.2.6 76 | 4.32.0 77 | 11 78 | 11 79 | 80 | 81 | 82 | scm:git:${project.scm.url} 83 | scm:git:${project.scm.url} 84 | https://github.com/liquibase/liquibase-hibernate.git 85 | HEAD 86 | 87 | 88 | 89 | 90 | org.hibernate.orm 91 | hibernate-core 92 | ${hibernate.version} 93 | 94 | 95 | org.hibernate.orm 96 | hibernate-envers 97 | ${hibernate.version} 98 | 99 | 100 | org.springframework 101 | spring-test 102 | ${spring.version} 103 | provided 104 | 105 | 106 | org.springframework 107 | spring-jdbc 108 | ${spring.version} 109 | provided 110 | 111 | 112 | org.springframework 113 | spring-beans 114 | ${spring.version} 115 | provided 116 | 117 | 118 | org.springframework 119 | spring-context 120 | ${spring.version} 121 | provided 122 | 123 | 124 | org.springframework 125 | spring-orm 126 | ${spring.version} 127 | provided 128 | 129 | 130 | com.h2database 131 | h2 132 | test 133 | 134 | 135 | org.hsqldb 136 | hsqldb 137 | test 138 | 139 | 140 | com.microsoft.sqlserver 141 | mssql-jdbc 142 | test 143 | 144 | 145 | com.oracle.database.jdbc 146 | ojdbc8 147 | test 148 | 149 | 150 | org.postgresql 151 | postgresql 152 | test 153 | 154 | 155 | org.projectlombok 156 | lombok 157 | 158 | 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-enforcer-plugin 165 | 166 | 167 | enforce-java 168 | compile 169 | 170 | enforce 171 | 172 | 173 | 174 | 175 | 11 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | maven-surefire-plugin 184 | ${maven-surefire-plugin.version} 185 | 186 | true 187 | plain 188 | 189 | ${project.build.directory}/spock-reports 190 | 191 | 192 | 193 | 194 | 195 | org.apache.maven.surefire 196 | surefire-junit47 197 | ${maven-surefire-plugin.version} 198 | 199 | 200 | 201 | 202 | maven-failsafe-plugin 203 | ${maven-failsafe-plugin.version} 204 | 205 | true 206 | plain 207 | 208 | ${project.build.directory}/spock-reports 209 | 210 | 211 | 212 | 213 | 214 | org.apache.maven.surefire 215 | surefire-junit47 216 | ${maven-surefire-plugin.version} 217 | 218 | 219 | 220 | 221 | org.apache.maven.plugins 222 | maven-javadoc-plugin 223 | ${maven-javadoc-plugin.version} 224 | 225 | 1.8 226 | false 227 | Liquibase ${project.name} ${project.version} API 228 | true 229 | none 230 | UTF-8 231 | ${project.build.directory} 232 | 233 | 234 | 235 | jar-javadoc 236 | 237 | jar 238 | 239 | package 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /pom.xml.releaseBackup: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.liquibase 7 | liquibase-parent-pom 8 | 0.5.8 9 | 10 | 11 | org.liquibase.ext 12 | liquibase-hibernate6 13 | 4.32.0-SNAPSHOT 14 | 15 | Liquibase Hibernate Integration 16 | Liquibase extension for hibernate integration including generating changesets based on changed 17 | hibernate mapping files 18 | 19 | https://github.com/liquibase/liquibase-hibernate/wiki 20 | 21 | 22 | Liquibase.org 23 | http://www.liquibase.org 24 | 25 | 26 | 27 | 28 | nvoxland 29 | Nathan Voxland 30 | nathan.voxland@liquibase.org 31 | 32 | architect 33 | developer 34 | 35 | -6 36 | 37 | 38 | randomeizer 39 | David Peterson 40 | david.peterson@servicerocket.com 41 | http://www.servicerocket.com/ 42 | 43 | developer 44 | 45 | 46 | 47 | fxbonnet 48 | Francois-Xavier Bonnet 49 | francois-xavier.bonnet@centraliens.net 50 | http://www.octo.com/ 51 | 52 | architect 53 | developer 54 | 55 | 56 | 57 | 58 | 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | Apache License, Version 2.0 62 | 63 | 64 | 65 | 66 | https://github.com/liquibase/liquibase-hibernate/issues 67 | 68 | 69 | 70 | https://github.com/liquibase/liquibase-hibernate/actions 71 | 72 | 73 | 74 | 6.6.15.Final 75 | 6.2.6 76 | 4.32.0 77 | 11 78 | 11 79 | 80 | 81 | 82 | scm:git:${project.scm.url} 83 | scm:git:${project.scm.url} 84 | https://github.com/liquibase/liquibase-hibernate.git 85 | HEAD 86 | 87 | 88 | 89 | 90 | org.hibernate.orm 91 | hibernate-core 92 | ${hibernate.version} 93 | 94 | 95 | org.hibernate.orm 96 | hibernate-envers 97 | ${hibernate.version} 98 | 99 | 100 | org.springframework 101 | spring-test 102 | ${spring.version} 103 | provided 104 | 105 | 106 | org.springframework 107 | spring-jdbc 108 | ${spring.version} 109 | provided 110 | 111 | 112 | org.springframework 113 | spring-beans 114 | ${spring.version} 115 | provided 116 | 117 | 118 | org.springframework 119 | spring-context 120 | ${spring.version} 121 | provided 122 | 123 | 124 | org.springframework 125 | spring-orm 126 | ${spring.version} 127 | provided 128 | 129 | 130 | com.h2database 131 | h2 132 | test 133 | 134 | 135 | org.hsqldb 136 | hsqldb 137 | test 138 | 139 | 140 | com.microsoft.sqlserver 141 | mssql-jdbc 142 | test 143 | 144 | 145 | com.oracle.database.jdbc 146 | ojdbc8 147 | test 148 | 149 | 150 | org.postgresql 151 | postgresql 152 | test 153 | 154 | 155 | org.projectlombok 156 | lombok 157 | 158 | 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-enforcer-plugin 165 | 166 | 167 | enforce-java 168 | compile 169 | 170 | enforce 171 | 172 | 173 | 174 | 175 | 11 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | maven-surefire-plugin 184 | ${maven-surefire-plugin.version} 185 | 186 | true 187 | plain 188 | 189 | ${project.build.directory}/spock-reports 190 | 191 | 192 | 193 | 194 | 195 | org.apache.maven.surefire 196 | surefire-junit47 197 | ${maven-surefire-plugin.version} 198 | 199 | 200 | 201 | 202 | maven-failsafe-plugin 203 | ${maven-failsafe-plugin.version} 204 | 205 | true 206 | plain 207 | 208 | ${project.build.directory}/spock-reports 209 | 210 | 211 | 212 | 213 | 214 | org.apache.maven.surefire 215 | surefire-junit47 216 | ${maven-surefire-plugin.version} 217 | 218 | 219 | 220 | 221 | org.apache.maven.plugins 222 | maven-javadoc-plugin 223 | ${maven-javadoc-plugin.version} 224 | 225 | 1.8 226 | false 227 | Liquibase ${project.name} ${project.version} API 228 | true 229 | none 230 | UTF-8 231 | ${project.build.directory} 232 | 233 | 234 | 235 | jar-javadoc 236 | 237 | jar 238 | 239 | package 240 | 241 | 242 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /release.properties: -------------------------------------------------------------------------------- 1 | #release configuration 2 | #Thu May 22 18:44:52 UTC 2025 3 | projectVersionPolicyId=default 4 | scm.branchCommitComment=@{prefix} prepare branch @{releaseLabel} 5 | pinExternals=false 6 | projectVersionPolicyConfig=${projectVersionPolicyConfig}\n 7 | exec.activateProfiles=github 8 | pushChanges=false 9 | project.rel.org.liquibase.ext\:liquibase-hibernate6=4.32.0 10 | scm.rollbackCommitComment=@{prefix} rollback the release of @{releaseLabel} 11 | remoteTagging=true 12 | scm.commentPrefix=[maven-release-plugin] 13 | releaseStrategyId=default 14 | project.scm.org.liquibase.ext\:liquibase-hibernate6.developerConnection=scm\:git\:${project.scm.url} 15 | project.scm.org.liquibase.ext\:liquibase-hibernate6.tag=HEAD 16 | completedPhase=end-release 17 | project.dev.org.liquibase.ext\:liquibase-hibernate6=4.33.0-SNAPSHOT 18 | scm.url=scm\:git\:https\://github.com/liquibase/liquibase-hibernate.git 19 | scm.developmentCommitComment=@{prefix} prepare for next development iteration 20 | exec.additionalArguments=-Dmaven.javadoc.skip\=true -Dmaven.test.skipTests\=true -Dmaven.test.skip\=true -Dmaven.deploy.skip\=true 21 | scm.tagNameFormat=@{project.artifactId}-@{project.version} 22 | project.scm.org.liquibase.ext\:liquibase-hibernate6.connection=scm\:git\:${project.scm.url} 23 | scm.tag=temp 24 | exec.snapshotReleasePluginAllowed=false 25 | preparationGoals=clean verify 26 | scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel} 27 | exec.pomFileName=pom.xml 28 | project.scm.org.liquibase.ext\:liquibase-hibernate6.url=https\://github.com/liquibase/liquibase-hibernate.git 29 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/customfactory/CustomMetadataFactory.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.customfactory; 2 | 3 | import liquibase.ext.hibernate.database.HibernateDatabase; 4 | import liquibase.ext.hibernate.database.connection.HibernateConnection; 5 | import org.hibernate.boot.Metadata; 6 | 7 | /** 8 | * Implement this interface to dynamically generate a hibernate:ejb3 configuration. 9 | * For example, if you create a class called com.example.hibernate.MyConfig, specify a url of hibernate:ejb3:com.example.hibernate.MyConfig. 10 | */ 11 | public interface CustomMetadataFactory { 12 | 13 | /* 14 | * Create a hibernate Configuration for the given database and connection. 15 | */ 16 | Metadata getMetadata(HibernateDatabase hibernateDatabase, HibernateConnection connection); 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/HibernateClassicDatabase.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.database.DatabaseConnection; 4 | import liquibase.exception.DatabaseException; 5 | import org.hibernate.boot.Metadata; 6 | import org.hibernate.boot.MetadataSources; 7 | import org.hibernate.cfg.AvailableSettings; 8 | import org.hibernate.cfg.Configuration; 9 | import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; 10 | import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; 11 | import org.hibernate.service.ServiceRegistry; 12 | 13 | /** 14 | * Database implementation for "classic" hibernate configurations. 15 | */ 16 | public class HibernateClassicDatabase extends HibernateDatabase { 17 | 18 | protected Configuration configuration; 19 | 20 | public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { 21 | return conn.getURL().startsWith("hibernate:classic:"); 22 | } 23 | 24 | @Override 25 | protected String findDialectName() { 26 | String dialectName = super.findDialectName(); 27 | 28 | if (dialectName == null) { 29 | dialectName = configuration.getProperty(AvailableSettings.DIALECT); 30 | } 31 | return dialectName; 32 | } 33 | 34 | 35 | protected Metadata buildMetadataFromPath() throws DatabaseException { 36 | this.configuration = new Configuration(); 37 | this.configuration.configure(getHibernateConnection().getPath()); 38 | 39 | return super.buildMetadataFromPath(); 40 | } 41 | 42 | @Override 43 | protected void configureSources(MetadataSources sources) throws DatabaseException { 44 | Configuration config = new Configuration(sources); 45 | config.configure(getHibernateConnection().getPath()); 46 | 47 | config.setProperty(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString()); 48 | config.setProperty("hibernate.cache.use_second_level_cache", "false"); 49 | 50 | ServiceRegistry standardRegistry = configuration.getStandardServiceRegistryBuilder() 51 | .applySettings(config.getProperties()) 52 | .addService(ConnectionProvider.class, new NoOpConnectionProvider()) 53 | .addService(MultiTenantConnectionProvider.class, new NoOpMultiTenantConnectionProvider()) 54 | .build(); 55 | 56 | config.buildSessionFactory(standardRegistry); 57 | } 58 | 59 | @Override 60 | public String getShortName() { 61 | return "hibernateClassic"; 62 | } 63 | 64 | @Override 65 | protected String getDefaultDatabaseProductName() { 66 | return "Hibernate Classic"; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/HibernateEjb3Database.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import java.lang.reflect.Field; 4 | import java.security.AccessController; 5 | import java.security.PrivilegedAction; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.hibernate.boot.Metadata; 10 | import org.hibernate.boot.MetadataSources; 11 | import org.hibernate.cfg.AvailableSettings; 12 | import org.hibernate.dialect.Dialect; 13 | import org.hibernate.jpa.HibernatePersistenceProvider; 14 | import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; 15 | import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; 16 | import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; 17 | 18 | import jakarta.persistence.EntityManagerFactory; 19 | import jakarta.persistence.metamodel.ManagedType; 20 | import jakarta.persistence.spi.PersistenceUnitTransactionType; 21 | import liquibase.Scope; 22 | import liquibase.database.DatabaseConnection; 23 | import liquibase.exception.DatabaseException; 24 | 25 | /** 26 | * Database implementation for "ejb3" hibernate configurations. 27 | */ 28 | public class HibernateEjb3Database extends HibernateDatabase { 29 | 30 | protected EntityManagerFactory entityManagerFactory; 31 | 32 | @Override 33 | public String getShortName() { 34 | return "hibernateEjb3"; 35 | } 36 | 37 | @Override 38 | protected String getDefaultDatabaseProductName() { 39 | return "Hibernate EJB3"; 40 | } 41 | 42 | 43 | @Override 44 | public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { 45 | return conn.getURL().startsWith("hibernate:ejb3:"); 46 | } 47 | 48 | /** 49 | * Calls {@link #createEntityManagerFactoryBuilder()} to create and save the entity manager factory. 50 | */ 51 | @Override 52 | protected Metadata buildMetadataFromPath() throws DatabaseException { 53 | 54 | EntityManagerFactoryBuilderImpl builder = createEntityManagerFactoryBuilder(); 55 | 56 | this.entityManagerFactory = builder.build(); 57 | 58 | Metadata metadata = builder.getMetadata(); 59 | 60 | String dialectString = findDialectName(); 61 | if (dialectString != null) { 62 | try { 63 | dialect = (Dialect) Class.forName(dialectString).newInstance(); 64 | Scope.getCurrentScope().getLog(getClass()).info("Using dialect " + dialectString); 65 | } catch (Exception e) { 66 | throw new DatabaseException(e); 67 | } 68 | } else { 69 | Scope.getCurrentScope().getLog(getClass()).info("Could not determine hibernate dialect, using HibernateGenericDialect"); 70 | dialect = new HibernateGenericDialect(); 71 | } 72 | 73 | return metadata; 74 | } 75 | 76 | protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() { 77 | MyHibernatePersistenceProvider persistenceProvider = new MyHibernatePersistenceProvider(); 78 | 79 | Map properties = new HashMap<>(); 80 | properties.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString()); 81 | properties.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString()); 82 | properties.put(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA)); 83 | 84 | final EntityManagerFactoryBuilderImpl builder = (EntityManagerFactoryBuilderImpl) persistenceProvider.getEntityManagerFactoryBuilderOrNull(getHibernateConnection().getPath(), properties, null); 85 | return builder; 86 | } 87 | 88 | @Override 89 | public String getProperty(String name) { 90 | String property = null; 91 | if (entityManagerFactory != null) { 92 | property = (String) entityManagerFactory.getProperties().get(name); 93 | } 94 | 95 | if (property == null) { 96 | return super.getProperty(name); 97 | } else { 98 | return property; 99 | } 100 | 101 | } 102 | 103 | @Override 104 | protected String findDialectName() { 105 | String dialectName = super.findDialectName(); 106 | if (dialectName != null) { 107 | return dialectName; 108 | } 109 | 110 | return (String) entityManagerFactory.getProperties().get(AvailableSettings.DIALECT); 111 | } 112 | 113 | /** 114 | * Adds sources based on what is in the saved entityManagerFactory 115 | */ 116 | @Override 117 | protected void configureSources(MetadataSources sources) throws DatabaseException { 118 | for (ManagedType managedType : entityManagerFactory.getMetamodel().getManagedTypes()) { 119 | Class javaType = managedType.getJavaType(); 120 | if (javaType == null) { 121 | continue; 122 | } 123 | sources.addAnnotatedClass(javaType); 124 | } 125 | 126 | Package[] packages = Package.getPackages(); 127 | for (Package p : packages) { 128 | sources.addPackage(p); 129 | } 130 | } 131 | 132 | private static class MyHibernatePersistenceProvider extends HibernatePersistenceProvider { 133 | 134 | private void setField(final Object obj, String fieldName, final Object value) throws Exception { 135 | final Field declaredField; 136 | 137 | declaredField = obj.getClass().getDeclaredField(fieldName); 138 | AccessController.doPrivileged(new PrivilegedAction() { 139 | @Override 140 | public Object run() { 141 | boolean wasAccessible = declaredField.isAccessible(); 142 | try { 143 | declaredField.setAccessible(true); 144 | declaredField.set(obj, value); 145 | return null; 146 | } catch (Exception ex) { 147 | throw new IllegalStateException("Cannot invoke method get", ex); 148 | } finally { 149 | declaredField.setAccessible(wasAccessible); 150 | } 151 | } 152 | }); 153 | } 154 | 155 | @Override 156 | protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String persistenceUnitName, Map properties, ClassLoader providedClassLoader) { 157 | return super.getEntityManagerFactoryBuilderOrNull(persistenceUnitName, properties, providedClassLoader); 158 | } 159 | 160 | @Override 161 | protected EntityManagerFactoryBuilder getEntityManagerFactoryBuilder(PersistenceUnitDescriptor persistenceUnitDescriptor, Map integration, ClassLoader providedClassLoader) { 162 | try { 163 | setField(persistenceUnitDescriptor, "jtaDataSource", null); 164 | setField(persistenceUnitDescriptor, "transactionType", PersistenceUnitTransactionType.RESOURCE_LOCAL); 165 | } catch (Exception ex) { 166 | Scope.getCurrentScope().getLog(getClass()).severe(null, ex); 167 | } 168 | return super.getEntityManagerFactoryBuilder(persistenceUnitDescriptor, integration, providedClassLoader); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/HibernateGenericDialect.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import org.hibernate.dialect.DatabaseVersion; 4 | import org.hibernate.dialect.Dialect; 5 | 6 | import liquibase.exception.DatabaseException; 7 | 8 | /** 9 | * Generic hibernate dialect used when an actual dialect cannot be determined. 10 | */ 11 | public class HibernateGenericDialect extends Dialect { 12 | public HibernateGenericDialect() throws DatabaseException { 13 | super(DatabaseVersion.make( 6, 1 )); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/HibernateSpringBeanDatabase.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.Scope; 4 | import liquibase.database.DatabaseConnection; 5 | import liquibase.exception.DatabaseException; 6 | import liquibase.ext.hibernate.database.connection.HibernateConnection; 7 | import org.hibernate.boot.Metadata; 8 | import org.hibernate.boot.MetadataSources; 9 | import org.springframework.beans.MutablePropertyValues; 10 | import org.springframework.beans.PropertyValue; 11 | import org.springframework.beans.factory.config.BeanDefinition; 12 | import org.springframework.beans.factory.config.TypedStringValue; 13 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 14 | import org.springframework.beans.factory.support.ManagedProperties; 15 | import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; 16 | import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; 17 | import org.springframework.core.io.ClassPathResource; 18 | import org.springframework.core.io.Resource; 19 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 20 | import org.springframework.core.io.support.ResourcePatternResolver; 21 | 22 | import java.net.URL; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Properties; 26 | 27 | /** 28 | * Database implementation for "spring" hibernate configurations where a bean name is given. If a package is used, {@link HibernateSpringPackageDatabase} will be used. 29 | */ 30 | public class HibernateSpringBeanDatabase extends HibernateDatabase { 31 | 32 | private BeanDefinition beanDefinition; 33 | private ManagedProperties beanDefinitionProperties; 34 | 35 | public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { 36 | return conn.getURL().startsWith("hibernate:spring:"); 37 | } 38 | 39 | /** 40 | * Calls {@link #loadBeanDefinition()} 41 | */ 42 | @Override 43 | protected Metadata buildMetadataFromPath() throws DatabaseException { 44 | loadBeanDefinition(); 45 | return super.buildMetadataFromPath(); 46 | } 47 | 48 | 49 | @Override 50 | public String getProperty(String name) { 51 | String value = super.getProperty(name); 52 | if (value == null && beanDefinitionProperties != null) { 53 | for (Map.Entry entry : ((ManagedProperties) beanDefinition.getPropertyValues().getPropertyValue("hibernateProperties").getValue()).entrySet()) { 54 | if (entry.getKey() instanceof TypedStringValue && entry.getValue() instanceof TypedStringValue) { 55 | if (((TypedStringValue) entry.getKey()).getValue().equals(name)) { 56 | return ((TypedStringValue) entry.getValue()).getValue(); 57 | } 58 | } 59 | } 60 | 61 | value = beanDefinitionProperties.getProperty(name); 62 | } 63 | return value; 64 | } 65 | 66 | /** 67 | * Parse the given URL assuming it is a spring XML file 68 | */ 69 | protected void loadBeanDefinition() throws DatabaseException { 70 | // Read configuration 71 | BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); 72 | XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry); 73 | reader.setNamespaceAware(true); 74 | HibernateConnection connection = getHibernateConnection(); 75 | reader.loadBeanDefinitions(new ClassPathResource(connection.getPath())); 76 | 77 | Properties props = connection.getProperties(); 78 | 79 | String beanName = props.getProperty("bean", null); 80 | 81 | if (beanName == null) { 82 | throw new IllegalStateException("A 'bean' name is required, definition in '" + connection.getPath() + "'."); 83 | } 84 | 85 | beanDefinition = registry.getBeanDefinition(beanName); 86 | if (beanDefinition == null) { 87 | throw new IllegalStateException("A bean named '" + beanName + "' could not be found in '" + connection.getPath() + "'."); 88 | } 89 | 90 | beanDefinitionProperties = (ManagedProperties) beanDefinition.getPropertyValues().getPropertyValue("hibernateProperties").getValue(); 91 | } 92 | 93 | @Override 94 | protected void configureSources(MetadataSources sources) throws DatabaseException { 95 | MutablePropertyValues properties = beanDefinition.getPropertyValues(); 96 | 97 | // Add annotated classes list. 98 | PropertyValue annotatedClassesProperty = properties.getPropertyValue("annotatedClasses"); 99 | if (annotatedClassesProperty != null) { 100 | List annotatedClasses = (List) annotatedClassesProperty.getValue(); 101 | if (annotatedClasses != null) { 102 | for (TypedStringValue className : annotatedClasses) { 103 | Scope.getCurrentScope().getLog(getClass()).info("Found annotated class " + className.getValue()); 104 | sources.addAnnotatedClass(findClass(className.getValue())); 105 | } 106 | } 107 | } 108 | 109 | try { 110 | // Add mapping locations 111 | PropertyValue mappingLocationsProp = properties.getPropertyValue("mappingLocations"); 112 | if (mappingLocationsProp != null) { 113 | List mappingLocations = (List) mappingLocationsProp.getValue(); 114 | if (mappingLocations != null) { 115 | ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 116 | for (TypedStringValue mappingLocation : mappingLocations) { 117 | Scope.getCurrentScope().getLog(getClass()).info("Found mappingLocation " + mappingLocation.getValue()); 118 | Resource[] resources = resourcePatternResolver.getResources(mappingLocation.getValue()); 119 | for (int i = 0; i < resources.length; i++) { 120 | URL url = resources[i].getURL(); 121 | Scope.getCurrentScope().getLog(getClass()).info("Adding resource " + url); 122 | sources.addURL(url); 123 | } 124 | } 125 | } 126 | } 127 | } catch (Exception e) { 128 | if (e instanceof DatabaseException) { 129 | throw (DatabaseException) e; 130 | } else { 131 | throw new DatabaseException(e); 132 | } 133 | } 134 | } 135 | 136 | private Class findClass(String className) { 137 | return findClass(className, Object.class); 138 | } 139 | 140 | private Class findClass(String className, Class superClass) { 141 | try { 142 | Class newClass = Class.forName(className); 143 | if (superClass.isAssignableFrom(newClass)) { 144 | return newClass.asSubclass(superClass); 145 | } else { 146 | throw new IllegalStateException("The provided class '" + className + "' is not assignable from the '" + superClass.getName() + "' superclass."); 147 | } 148 | } catch (ClassNotFoundException e) { 149 | throw new IllegalStateException("Unable to find required class: '" + className + "'. Please check classpath and class name."); 150 | } 151 | } 152 | 153 | @Override 154 | public String getShortName() { 155 | return "hibernateSpringBean"; 156 | } 157 | 158 | @Override 159 | protected String getDefaultDatabaseProductName() { 160 | return "Hibernate Spring Bean"; 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/HibernateSpringPackageDatabase.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.List; 8 | 9 | import org.hibernate.bytecode.enhance.spi.EnhancementContext; 10 | import org.hibernate.cfg.AvailableSettings; 11 | import org.hibernate.envers.configuration.EnversSettings; 12 | import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; 13 | import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor; 14 | import org.hibernate.jpa.boot.spi.Bootstrap; 15 | import org.springframework.core.io.ClassPathResource; 16 | import org.springframework.core.io.DefaultResourceLoader; 17 | import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; 18 | import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo; 19 | import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 20 | import org.springframework.core.NativeDetector; 21 | import jakarta.persistence.spi.PersistenceUnitInfo; 22 | import liquibase.Scope; 23 | import liquibase.database.DatabaseConnection; 24 | import liquibase.database.jvm.JdbcConnection; 25 | import liquibase.exception.DatabaseException; 26 | import liquibase.ext.hibernate.database.connection.HibernateConnection; 27 | 28 | /** 29 | * Database implementation for "spring" hibernate configurations that scans packages. If specifying a bean, {@link HibernateSpringBeanDatabase} is used. 30 | */ 31 | public class HibernateSpringPackageDatabase extends JpaPersistenceDatabase { 32 | 33 | @Override 34 | public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { 35 | return conn.getURL().startsWith("hibernate:spring:") && !isXmlFile(conn); 36 | } 37 | 38 | @Override 39 | public int getPriority() { 40 | return super.getPriority() + 10; //want this to be picked over HibernateSpringBeanDatabase if it is not xml file 41 | } 42 | 43 | /** 44 | * Return true if the given path is a spring XML file. 45 | */ 46 | protected boolean isXmlFile(DatabaseConnection connection) { 47 | HibernateConnection hibernateConnection; 48 | if (connection instanceof JdbcConnection) { 49 | hibernateConnection = ((HibernateConnection) ((JdbcConnection) connection).getUnderlyingConnection()); 50 | } else if (connection instanceof HibernateConnection) { 51 | hibernateConnection = (HibernateConnection) connection; 52 | } else { 53 | return false; 54 | } 55 | 56 | 57 | String path = hibernateConnection.getPath(); 58 | if (path.contains("/")) { 59 | return true; 60 | } 61 | ClassPathResource resource = new ClassPathResource(path); 62 | try { 63 | if (resource.exists() && !resource.getFile().isDirectory()) { 64 | return true; 65 | } else { 66 | return false; 67 | } 68 | } catch (IOException e) { 69 | return false; 70 | } 71 | 72 | 73 | } 74 | 75 | @Override 76 | protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() { 77 | DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager(); 78 | internalPersistenceUnitManager.setResourceLoader(new DefaultResourceLoader(Scope.getCurrentScope().getClassLoader())); 79 | 80 | String[] packagesToScan = getHibernateConnection().getPath().split(","); 81 | 82 | for (String packageName : packagesToScan) { 83 | Scope.getCurrentScope().getLog(getClass()).info("Found package " + packageName); 84 | } 85 | 86 | internalPersistenceUnitManager.setPackagesToScan(packagesToScan); 87 | 88 | internalPersistenceUnitManager.preparePersistenceUnitInfos(); 89 | PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo(); 90 | HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(); 91 | 92 | if (persistenceUnitInfo instanceof SmartPersistenceUnitInfo) { 93 | ((SmartPersistenceUnitInfo) persistenceUnitInfo).setPersistenceProviderPackageName(jpaVendorAdapter.getPersistenceProviderRootPackage()); 94 | } 95 | 96 | Map map = new HashMap<>(); 97 | map.put(AvailableSettings.DIALECT, getProperty(AvailableSettings.DIALECT)); 98 | map.put(HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString()); 99 | map.put(AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.FALSE.toString()); 100 | map.put(AvailableSettings.PHYSICAL_NAMING_STRATEGY, getHibernateConnection().getProperties().getProperty(AvailableSettings.PHYSICAL_NAMING_STRATEGY)); 101 | map.put(AvailableSettings.IMPLICIT_NAMING_STRATEGY, getHibernateConnection().getProperties().getProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY)); 102 | map.put(AvailableSettings.SCANNER_DISCOVERY, ""); // disable scanning of all classes and hbm.xml files. Only scan speficied packages 103 | map.put(EnversSettings.AUDIT_TABLE_PREFIX,getHibernateConnection().getProperties().getProperty(EnversSettings.AUDIT_TABLE_PREFIX,"")); 104 | map.put(EnversSettings.AUDIT_TABLE_SUFFIX,getHibernateConnection().getProperties().getProperty(EnversSettings.AUDIT_TABLE_SUFFIX,"_AUD")); 105 | map.put(EnversSettings.REVISION_FIELD_NAME,getHibernateConnection().getProperties().getProperty(EnversSettings.REVISION_FIELD_NAME,"REV")); 106 | map.put(EnversSettings.REVISION_TYPE_FIELD_NAME,getHibernateConnection().getProperties().getProperty(EnversSettings.REVISION_TYPE_FIELD_NAME,"REVTYPE")); 107 | map.put(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA, getProperty(AvailableSettings.USE_NATIONALIZED_CHARACTER_DATA)); 108 | map.put(AvailableSettings.TIMEZONE_DEFAULT_STORAGE, getProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE)); 109 | PersistenceUnitInfoDescriptor persistenceUnitInfoDescriptor = createPersistenceUnitInfoDescriptor(persistenceUnitInfo); 110 | EntityManagerFactoryBuilderImpl builder = (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfoDescriptor, map); 111 | 112 | return builder; 113 | } 114 | 115 | 116 | public PersistenceUnitInfoDescriptor createPersistenceUnitInfoDescriptor(PersistenceUnitInfo info) { 117 | final List mergedClassesAndPackages = new ArrayList<>(info.getManagedClassNames()); 118 | if (info instanceof SmartPersistenceUnitInfo ) { 119 | mergedClassesAndPackages.addAll(((SmartPersistenceUnitInfo)info).getManagedPackages()); 120 | } 121 | return 122 | new PersistenceUnitInfoDescriptor(info) { 123 | @Override 124 | public List getManagedClassNames() { 125 | return mergedClassesAndPackages; 126 | } 127 | 128 | @Override 129 | public void pushClassTransformer(EnhancementContext enhancementContext) { 130 | if (!NativeDetector.inNativeImage()) { 131 | super.pushClassTransformer(enhancementContext); 132 | } 133 | } 134 | }; 135 | } 136 | 137 | 138 | @Override 139 | public String getShortName() { 140 | return "hibernateSpringPackage"; 141 | } 142 | 143 | @Override 144 | protected String getDefaultDatabaseProductName() { 145 | return "Hibernate Spring Package"; 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/JpaPersistenceDatabase.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import java.util.Map; 4 | 5 | import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; 6 | import org.hibernate.jpa.boot.spi.Bootstrap; 7 | import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager; 8 | 9 | import jakarta.persistence.spi.PersistenceUnitInfo; 10 | import liquibase.database.DatabaseConnection; 11 | import liquibase.exception.DatabaseException; 12 | import liquibase.ext.hibernate.database.connection.HibernateDriver; 13 | 14 | /** 15 | * Database implementation for JPA configurations. 16 | * This supports passing a JPA persistence XML file reference. 17 | */ 18 | public class JpaPersistenceDatabase extends HibernateEjb3Database { 19 | 20 | @Override 21 | public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException { 22 | return conn.getURL().startsWith("jpa:persistence:"); 23 | } 24 | 25 | @Override 26 | public String getDefaultDriver(String url) { 27 | if (url.startsWith("jpa:persistence:")) { 28 | return HibernateDriver.class.getName(); 29 | } 30 | return null; 31 | } 32 | 33 | @Override 34 | public String getShortName() { 35 | return "jpaPersistence"; 36 | } 37 | 38 | @Override 39 | protected String getDefaultDatabaseProductName() { 40 | return "JPA Persistence"; 41 | } 42 | 43 | @Override 44 | protected EntityManagerFactoryBuilderImpl createEntityManagerFactoryBuilder() { 45 | DefaultPersistenceUnitManager internalPersistenceUnitManager = new DefaultPersistenceUnitManager(); 46 | 47 | internalPersistenceUnitManager.setPersistenceXmlLocation(getHibernateConnection().getPath()); 48 | internalPersistenceUnitManager.setDefaultPersistenceUnitRootLocation(null); 49 | 50 | internalPersistenceUnitManager.preparePersistenceUnitInfos(); 51 | PersistenceUnitInfo persistenceUnitInfo = internalPersistenceUnitManager.obtainDefaultPersistenceUnitInfo(); 52 | 53 | EntityManagerFactoryBuilderImpl builder = (EntityManagerFactoryBuilderImpl) Bootstrap.getEntityManagerFactoryBuilder(persistenceUnitInfo, Map.of( 54 | HibernateDatabase.HIBERNATE_TEMP_USE_JDBC_METADATA_DEFAULTS, Boolean.FALSE.toString())); 55 | return builder; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/NoOpConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | 8 | /** 9 | * Used by hibernate to ensure no database access is performed. 10 | */ 11 | class NoOpConnectionProvider implements ConnectionProvider { 12 | 13 | @Override 14 | public Connection getConnection() throws SQLException { 15 | throw new SQLException("No connection"); 16 | } 17 | 18 | @Override 19 | public void closeConnection(Connection conn) throws SQLException { 20 | } 21 | 22 | @Override 23 | public boolean supportsAggressiveRelease() { 24 | return false; 25 | } 26 | 27 | @Override 28 | public boolean isUnwrappableAs(Class unwrapType) { 29 | return false; 30 | } 31 | 32 | @Override 33 | public T unwrap(Class unwrapType) { 34 | return null; 35 | } 36 | 37 | 38 | public Connection getConnection(String tenantIdentifier) throws SQLException { 39 | return getConnection(); 40 | } 41 | 42 | public Connection getConnection(Object o) throws SQLException { 43 | return getConnection(); 44 | } 45 | 46 | public void releaseConnection(Object tenantIdentifier, Connection connection) throws SQLException { 47 | 48 | } 49 | 50 | public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/NoOpMultiTenantConnectionProvider.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; 4 | 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | 8 | /** 9 | * Used by hibernate to ensure no database access is performed. 10 | */ 11 | class NoOpMultiTenantConnectionProvider implements MultiTenantConnectionProvider { 12 | 13 | @Override 14 | public boolean isUnwrappableAs(Class unwrapType) { 15 | return false; 16 | } 17 | 18 | @Override 19 | public T unwrap(Class unwrapType) { 20 | return null; 21 | } 22 | 23 | @Override 24 | public Connection getAnyConnection() throws SQLException { 25 | return null; 26 | } 27 | 28 | @Override 29 | public void releaseAnyConnection(Connection connection) throws SQLException { 30 | 31 | } 32 | 33 | public Connection getConnection(String s) throws SQLException { 34 | return null; 35 | } 36 | 37 | public void releaseConnection(String s, Connection connection) throws SQLException { 38 | 39 | } 40 | 41 | public Connection getConnection(Object tenantIdentifier) throws SQLException { 42 | return null; 43 | } 44 | 45 | public void releaseConnection(Object tenantIdentifier, Connection connection) throws SQLException { 46 | 47 | } 48 | 49 | @Override 50 | public boolean supportsAggressiveRelease() { 51 | return false; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/connection/HibernateConnection.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database.connection; 2 | 3 | import liquibase.resource.ResourceAccessor; 4 | 5 | import java.io.IOException; 6 | import java.io.StringReader; 7 | import java.net.URLDecoder; 8 | import java.sql.*; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | import java.util.concurrent.Executor; 12 | 13 | /** 14 | * Implements java.sql.Connection in order to pretend a hibernate configuration is a database in order to fit into the Liquibase framework. 15 | * Beyond standard Connection methods, this class exposes {@link #getPrefix()}, {@link #getPath()} and {@link #getProperties()} to access the setting passed in the JDBC URL. 16 | */ 17 | public class HibernateConnection implements Connection { 18 | private String prefix; 19 | private String url; 20 | 21 | private String path; 22 | private ResourceAccessor resourceAccessor; 23 | private Properties properties; 24 | 25 | public HibernateConnection(String url, ResourceAccessor resourceAccessor) { 26 | this.url = url; 27 | 28 | this.prefix = url.replaceFirst(":[^:]+$", ""); 29 | 30 | // Trim the prefix off the URL for the path 31 | path = url.substring(prefix.length() + 1); 32 | this.resourceAccessor = resourceAccessor; 33 | 34 | // Check if there is a parameter/query string value. 35 | properties = new Properties(); 36 | 37 | int queryIndex = path.indexOf('?'); 38 | if (queryIndex >= 0) { 39 | // Convert the query string into properties 40 | properties.putAll(readProperties(path.substring(queryIndex + 1))); 41 | 42 | if (properties.containsKey("dialect") && !properties.containsKey("hibernate.dialect")) { 43 | properties.put("hibernate.dialect", properties.getProperty("dialect")); 44 | } 45 | 46 | // Remove the query string 47 | path = path.substring(0, queryIndex); 48 | } 49 | } 50 | 51 | /** 52 | * Creates properties to attach to this connection based on the passed query string. 53 | */ 54 | protected Properties readProperties(String queryString) { 55 | Properties properties = new Properties(); 56 | queryString = queryString.replaceAll("&", System.getProperty("line.separator")); 57 | try { 58 | queryString = URLDecoder.decode(queryString, "UTF-8"); 59 | properties.load(new StringReader(queryString)); 60 | } catch (IOException ioe) { 61 | throw new IllegalStateException("Failed to read properties from url", ioe); 62 | } 63 | 64 | return properties; 65 | } 66 | 67 | /** 68 | * Returns the entire connection URL 69 | */ 70 | public String getUrl() { 71 | return url; 72 | } 73 | 74 | 75 | /** 76 | * Returns the 'protocol' of the URL. For example, "hibernate:classic" or "hibernate:ejb3" 77 | */ 78 | public String getPrefix() { 79 | return prefix; 80 | } 81 | 82 | /** 83 | * The portion of the url between the path and the query string. Normally a filename or a class name. 84 | */ 85 | public String getPath() { 86 | return path; 87 | } 88 | 89 | /** 90 | * The set of properties provided by the URL. Eg: 91 | *

92 | * hibernate:classic:/path/to/hibernate.cfg.xml?foo=bar 93 | *

94 | * This will have a property called 'foo' with a value of 'bar'. 95 | */ 96 | public Properties getProperties() { 97 | return properties; 98 | } 99 | 100 | 101 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 102 | /// JDBC METHODS 103 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 104 | 105 | public Statement createStatement() throws SQLException { 106 | return null; 107 | } 108 | 109 | public PreparedStatement prepareStatement(String sql) throws SQLException { 110 | return null; 111 | } 112 | 113 | public CallableStatement prepareCall(String sql) throws SQLException { 114 | return null; 115 | } 116 | 117 | public String nativeSQL(String sql) throws SQLException { 118 | return null; 119 | } 120 | 121 | public void setAutoCommit(boolean autoCommit) throws SQLException { 122 | 123 | } 124 | 125 | public boolean getAutoCommit() throws SQLException { 126 | return false; 127 | } 128 | 129 | public void commit() throws SQLException { 130 | 131 | } 132 | 133 | public void rollback() throws SQLException { 134 | 135 | } 136 | 137 | public void close() throws SQLException { 138 | 139 | } 140 | 141 | public boolean isClosed() throws SQLException { 142 | return false; 143 | } 144 | 145 | public DatabaseMetaData getMetaData() throws SQLException { 146 | return new HibernateConnectionMetadata(url); 147 | } 148 | 149 | public void setReadOnly(boolean readOnly) throws SQLException { 150 | 151 | } 152 | 153 | public boolean isReadOnly() throws SQLException { 154 | return true; 155 | } 156 | 157 | public void setCatalog(String catalog) throws SQLException { 158 | 159 | } 160 | 161 | public String getCatalog() throws SQLException { 162 | return "HIBERNATE"; 163 | } 164 | 165 | public void setTransactionIsolation(int level) throws SQLException { 166 | 167 | } 168 | 169 | public int getTransactionIsolation() throws SQLException { 170 | return 0; 171 | } 172 | 173 | public SQLWarning getWarnings() throws SQLException { 174 | return null; 175 | } 176 | 177 | public void clearWarnings() throws SQLException { 178 | 179 | } 180 | 181 | public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { 182 | return null; 183 | } 184 | 185 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 186 | return null; 187 | } 188 | 189 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { 190 | return null; 191 | } 192 | 193 | public Map> getTypeMap() throws SQLException { 194 | return null; 195 | } 196 | 197 | public void setTypeMap(Map> map) throws SQLException { 198 | 199 | } 200 | 201 | public void setHoldability(int holdability) throws SQLException { 202 | 203 | } 204 | 205 | public int getHoldability() throws SQLException { 206 | return 0; 207 | } 208 | 209 | public Savepoint setSavepoint() throws SQLException { 210 | return null; 211 | } 212 | 213 | public Savepoint setSavepoint(String name) throws SQLException { 214 | return null; 215 | } 216 | 217 | public void rollback(Savepoint savepoint) throws SQLException { 218 | 219 | } 220 | 221 | public void releaseSavepoint(Savepoint savepoint) throws SQLException { 222 | 223 | } 224 | 225 | public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 226 | return null; 227 | } 228 | 229 | public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 230 | return null; 231 | } 232 | 233 | public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { 234 | return null; 235 | } 236 | 237 | public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { 238 | return null; 239 | } 240 | 241 | public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { 242 | return null; 243 | } 244 | 245 | public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { 246 | return null; 247 | } 248 | 249 | public Clob createClob() throws SQLException { 250 | return null; 251 | } 252 | 253 | public Blob createBlob() throws SQLException { 254 | return null; 255 | } 256 | 257 | public NClob createNClob() throws SQLException { 258 | return null; 259 | } 260 | 261 | public SQLXML createSQLXML() throws SQLException { 262 | return null; 263 | } 264 | 265 | public boolean isValid(int timeout) throws SQLException { 266 | return false; 267 | } 268 | 269 | public void setClientInfo(String name, String value) throws SQLClientInfoException { 270 | 271 | } 272 | 273 | public void setClientInfo(Properties properties) throws SQLClientInfoException { 274 | 275 | } 276 | 277 | public String getClientInfo(String name) throws SQLException { 278 | return null; 279 | } 280 | 281 | public Properties getClientInfo() throws SQLException { 282 | return null; 283 | } 284 | 285 | public Array createArrayOf(String typeName, Object[] elements) throws SQLException { 286 | return null; 287 | } 288 | 289 | public Struct createStruct(String typeName, Object[] attributes) throws SQLException { 290 | return null; 291 | } 292 | 293 | public T unwrap(Class iface) throws SQLException { 294 | return null; 295 | } 296 | 297 | public boolean isWrapperFor(Class iface) throws SQLException { 298 | return false; 299 | } 300 | 301 | //@Override only in java 1.7 302 | public void abort(Executor arg0) throws SQLException { 303 | } 304 | 305 | //@Override only in java 1.7 306 | public int getNetworkTimeout() throws SQLException { 307 | return 0; 308 | } 309 | 310 | //@Override only in java 1.7 311 | public String getSchema() throws SQLException { 312 | return "HIBERNATE"; 313 | } 314 | 315 | //@Override only in java 1.7 316 | public void setNetworkTimeout(Executor arg0, int arg1) throws SQLException { 317 | } 318 | 319 | //@Override only in java 1.7 320 | public void setSchema(String arg0) throws SQLException { 321 | } 322 | 323 | public ResourceAccessor getResourceAccessor() { 324 | return resourceAccessor; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/database/connection/HibernateDriver.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database.connection; 2 | 3 | import liquibase.database.LiquibaseExtDriver; 4 | import liquibase.resource.ResourceAccessor; 5 | 6 | import java.sql.*; 7 | import java.util.Properties; 8 | import java.util.logging.Logger; 9 | 10 | /** 11 | * Implements the standard java.sql.Driver interface to allow the Hibernate integration to better fit into 12 | * what Liquibase expects. 13 | */ 14 | public class HibernateDriver implements Driver, LiquibaseExtDriver { 15 | 16 | private ResourceAccessor resourceAccessor; 17 | 18 | public Connection connect(String url, Properties info) throws SQLException { 19 | return new HibernateConnection(url, resourceAccessor); 20 | } 21 | 22 | public boolean acceptsURL(String url) throws SQLException { 23 | return url.startsWith("hibernate:"); 24 | } 25 | 26 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { 27 | return new DriverPropertyInfo[0]; 28 | } 29 | 30 | public int getMajorVersion() { 31 | return 0; 32 | } 33 | 34 | public int getMinorVersion() { 35 | return 0; 36 | } 37 | 38 | public boolean jdbcCompliant() { 39 | return false; 40 | } 41 | 42 | //@Override only override for java 1.7 43 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 44 | throw new SQLFeatureNotSupportedException(); 45 | } 46 | 47 | @Override 48 | public void setResourceAccessor(ResourceAccessor accessor) { 49 | this.resourceAccessor = accessor; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/ChangedColumnChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.Difference; 6 | import liquibase.diff.ObjectDifferences; 7 | import liquibase.diff.output.DiffOutputControl; 8 | import liquibase.ext.hibernate.database.HibernateDatabase; 9 | import liquibase.statement.DatabaseFunction; 10 | import liquibase.structure.DatabaseObject; 11 | import liquibase.structure.core.Column; 12 | import liquibase.structure.core.DataType; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Hibernate and database types tend to look different even though they are not. 18 | * The only change that we are handling it size change, and even for this one there are exceptions. 19 | */ 20 | public class ChangedColumnChangeGenerator extends liquibase.diff.output.changelog.core.ChangedColumnChangeGenerator { 21 | 22 | private static final List TYPES_TO_IGNORE_SIZE = List.of("TIMESTAMP", "TIME"); 23 | 24 | @Override 25 | public int getPriority(Class objectType, Database database) { 26 | if (Column.class.isAssignableFrom(objectType)) { 27 | return PRIORITY_ADDITIONAL; 28 | } 29 | return PRIORITY_NONE; 30 | } 31 | 32 | @Override 33 | protected void handleTypeDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List changes, Database referenceDatabase, Database comparisonDatabase) { 34 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 35 | handleSizeChange(column, differences, control, changes, referenceDatabase, comparisonDatabase); 36 | } else { 37 | super.handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); 38 | } 39 | } 40 | 41 | private void handleSizeChange(Column column, ObjectDifferences differences, DiffOutputControl control, List changes, Database referenceDatabase, Database comparisonDatabase) { 42 | if (TYPES_TO_IGNORE_SIZE.stream().anyMatch(s -> s.equalsIgnoreCase(column.getType().getTypeName()))) { 43 | return; 44 | } 45 | Difference difference = differences.getDifference("type"); 46 | if (difference != null) { 47 | for (Difference d : differences.getDifferences()) { 48 | if (!(d.getReferenceValue() instanceof DataType)) { 49 | differences.removeDifference(d.getField()); 50 | continue; 51 | } 52 | Integer originalSize = ((DataType) d.getReferenceValue()).getColumnSize(); 53 | Integer newSize = ((DataType) d.getComparedValue()).getColumnSize(); 54 | if (newSize == null || originalSize == null || newSize.equals(originalSize)) { 55 | differences.removeDifference(d.getField()); 56 | } 57 | } 58 | super.handleTypeDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); 59 | } 60 | } 61 | 62 | @Override 63 | protected void handleDefaultValueDifferences(Column column, ObjectDifferences differences, DiffOutputControl control, List changes, Database referenceDatabase, Database comparisonDatabase) { 64 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 65 | Difference difference = differences.getDifference("defaultValue"); 66 | if (difference != null && difference.getReferenceValue() == null && difference.getComparedValue() instanceof DatabaseFunction) { 67 | //database sometimes adds a function default value, like for timestamp columns 68 | return; 69 | } 70 | difference = differences.getDifference("defaultValue"); 71 | if (difference != null) { 72 | super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); 73 | } 74 | // do nothing, types tend to not match with hibernate 75 | } 76 | super.handleDefaultValueDifferences(column, differences, control, changes, referenceDatabase, comparisonDatabase); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/ChangedForeignKeyChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.ObjectDifferences; 6 | import liquibase.diff.output.DiffOutputControl; 7 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 8 | import liquibase.ext.hibernate.database.HibernateDatabase; 9 | import liquibase.structure.DatabaseObject; 10 | import liquibase.structure.core.ForeignKey; 11 | 12 | /** 13 | * Hibernate doesn't know about all the variations that occur with foreign keys but just whether the FK exists or not. 14 | * To prevent changing customized foreign keys, we suppress all foreign key changes from hibernate. 15 | */ 16 | public class ChangedForeignKeyChangeGenerator extends liquibase.diff.output.changelog.core.ChangedForeignKeyChangeGenerator { 17 | 18 | @Override 19 | public int getPriority(Class objectType, Database database) { 20 | if (ForeignKey.class.isAssignableFrom(objectType)) { 21 | return PRIORITY_ADDITIONAL; 22 | } 23 | return PRIORITY_NONE; 24 | } 25 | 26 | @Override 27 | public Change[] fixChanged(DatabaseObject changedObject, ObjectDifferences differences, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 28 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 29 | differences.removeDifference("deleteRule"); 30 | differences.removeDifference("updateRule"); 31 | differences.removeDifference("validate"); 32 | if (!differences.hasDifferences()) { 33 | return null; 34 | } 35 | } 36 | 37 | return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/ChangedPrimaryKeyChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.ObjectDifferences; 6 | import liquibase.diff.output.DiffOutputControl; 7 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 8 | import liquibase.ext.hibernate.database.HibernateDatabase; 9 | import liquibase.structure.DatabaseObject; 10 | import liquibase.structure.core.PrimaryKey; 11 | 12 | /** 13 | * Hibernate doesn't know about all the variations that occur with primary keys, especially backing index stuff. 14 | * To prevent changing customized primary keys, we suppress this kind of changes from hibernate side. 15 | */ 16 | public class ChangedPrimaryKeyChangeGenerator extends liquibase.diff.output.changelog.core.ChangedPrimaryKeyChangeGenerator { 17 | 18 | @Override 19 | public int getPriority(Class objectType, Database database) { 20 | if (PrimaryKey.class.isAssignableFrom(objectType)) { 21 | return PRIORITY_ADDITIONAL; 22 | } 23 | return PRIORITY_NONE; 24 | } 25 | 26 | @Override 27 | public Change[] fixChanged(DatabaseObject changedObject, ObjectDifferences differences, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 28 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 29 | differences.removeDifference("unique"); 30 | differences.removeDifference("validate"); 31 | if (!differences.hasDifferences()) { 32 | return null; 33 | } 34 | } 35 | 36 | return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/ChangedSequenceChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.Difference; 6 | import liquibase.diff.ObjectDifferences; 7 | import liquibase.diff.output.DiffOutputControl; 8 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 9 | import liquibase.ext.hibernate.database.HibernateDatabase; 10 | import liquibase.structure.DatabaseObject; 11 | import liquibase.structure.core.Sequence; 12 | 13 | import java.util.Collections; 14 | import java.util.HashSet; 15 | import java.util.LinkedHashSet; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * Hibernate manages sequences only by the name, startValue and incrementBy fields. 21 | * However, non-hibernate databases might return default values for other fields triggering false positives. 22 | */ 23 | public class ChangedSequenceChangeGenerator extends liquibase.diff.output.changelog.core.ChangedSequenceChangeGenerator { 24 | 25 | private static final Set HIBERNATE_SEQUENCE_FIELDS; 26 | 27 | static { 28 | HashSet hibernateSequenceFields = new HashSet<>(); 29 | hibernateSequenceFields.add("name"); 30 | hibernateSequenceFields.add("startValue"); 31 | hibernateSequenceFields.add("incrementBy"); 32 | HIBERNATE_SEQUENCE_FIELDS = Collections.unmodifiableSet(hibernateSequenceFields); 33 | } 34 | 35 | @Override 36 | public int getPriority(Class objectType, Database database) { 37 | if (Sequence.class.isAssignableFrom(objectType)) { 38 | return PRIORITY_ADDITIONAL; 39 | } 40 | return PRIORITY_NONE; 41 | } 42 | 43 | @Override 44 | public Change[] fixChanged(DatabaseObject changedObject, ObjectDifferences differences, DiffOutputControl control, 45 | Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 46 | if (!(referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase)) { 47 | return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain); 48 | } 49 | 50 | // if any of the databases is a hibernate database, remove all differences that affect a field not managed by hibernate 51 | Set ignoredDifferenceFields = differences.getDifferences().stream() 52 | .map(Difference::getField) 53 | .filter(differenceField -> !HIBERNATE_SEQUENCE_FIELDS.contains(differenceField)) 54 | .collect(Collectors.toCollection(LinkedHashSet::new)); 55 | ignoredDifferenceFields.forEach(differences::removeDifference); 56 | this.advancedIgnoredDifferenceFields(differences, referenceDatabase, comparisonDatabase); 57 | return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain); 58 | } 59 | 60 | 61 | /** 62 | * In some cases a value that was 1 can be null in the database, or the name field can be different only by case. 63 | * This method removes these differences from the list of differences so we don't generate a change for them. 64 | */ 65 | private void advancedIgnoredDifferenceFields(ObjectDifferences differences, Database referenceDatabase, Database comparisonDatabase) { 66 | Set ignoredDifferenceFields = new HashSet<>(); 67 | for (Difference difference : differences.getDifferences()) { 68 | String field = difference.getField(); 69 | String refValue = difference.getReferenceValue() != null ? difference.getReferenceValue().toString() : null; 70 | String comparedValue = difference.getComparedValue() != null ? difference.getComparedValue().toString() : null; 71 | 72 | // if the name field case is different and the databases are case-insensitive, we can ignore the difference 73 | boolean isNameField = field.equals("name"); 74 | boolean isCaseInsensitive = !referenceDatabase.isCaseSensitive() || !comparisonDatabase.isCaseSensitive(); 75 | 76 | // if the startValue or incrementBy fields are 1 and the other is null, we can ignore the difference 77 | // Or 50, as it is the default value for hibernate for allocationSize: 78 | // https://github.com/hibernate/hibernate-orm/blob/bda95dfbe75c68f5c1b77a2f21c403cbe08548a2/hibernate-core/src/main/java/org/hibernate/boot/model/IdentifierGeneratorDefinition.java#L252 79 | boolean isStartOrIncrementField = field.equals("startValue") || field.equals("incrementBy"); 80 | boolean isOneOrFiftyAndNull = "1".equals(refValue) && comparedValue == null || refValue == null && "1".equals(comparedValue) || 81 | "50".equals(refValue) && comparedValue == null || refValue == null && "50".equals(comparedValue); 82 | 83 | if ((isNameField && isCaseInsensitive && refValue != null && refValue.equalsIgnoreCase(comparedValue)) || 84 | (isStartOrIncrementField && isOneOrFiftyAndNull)) { 85 | ignoredDifferenceFields.add(field); 86 | } 87 | } 88 | ignoredDifferenceFields.forEach(differences::removeDifference); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/ChangedUniqueConstraintChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.ObjectDifferences; 6 | import liquibase.diff.output.DiffOutputControl; 7 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 8 | import liquibase.ext.hibernate.database.HibernateDatabase; 9 | import liquibase.structure.DatabaseObject; 10 | import liquibase.structure.core.UniqueConstraint; 11 | 12 | /** 13 | * Unique attribute for unique constraints backing index can have different values dependending on the database implementation, 14 | * so we suppress all unique constraint changes based on unique constraints. 15 | 16 | */ 17 | public class ChangedUniqueConstraintChangeGenerator extends liquibase.diff.output.changelog.core.ChangedUniqueConstraintChangeGenerator { 18 | 19 | @Override 20 | public int getPriority(Class objectType, Database database) { 21 | if (UniqueConstraint.class.isAssignableFrom(objectType)) { 22 | return PRIORITY_ADDITIONAL; 23 | } 24 | return PRIORITY_NONE; 25 | } 26 | 27 | @Override 28 | public Change[] fixChanged(DatabaseObject changedObject, ObjectDifferences differences, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 29 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 30 | differences.removeDifference("unique"); 31 | if (!differences.hasDifferences()) { 32 | return null; 33 | } 34 | } 35 | return super.fixChanged(changedObject, differences, control, referenceDatabase, comparisonDatabase, chain); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/MissingSequenceChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.output.DiffOutputControl; 6 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 7 | import liquibase.ext.hibernate.database.HibernateDatabase; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.Sequence; 10 | 11 | public class MissingSequenceChangeGenerator extends liquibase.diff.output.changelog.core.MissingSequenceChangeGenerator { 12 | 13 | @Override 14 | public int getPriority(Class objectType, Database database) { 15 | if (Sequence.class.isAssignableFrom(objectType)) { 16 | return PRIORITY_ADDITIONAL; 17 | } 18 | return PRIORITY_NONE; 19 | } 20 | 21 | @Override 22 | public Change[] fixMissing(DatabaseObject missingObject, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 23 | if (referenceDatabase instanceof HibernateDatabase && !comparisonDatabase.supportsSequences()) { 24 | return null; 25 | } else if (comparisonDatabase instanceof HibernateDatabase && !referenceDatabase.supportsSequences()) { 26 | return null; 27 | }else { 28 | return super.fixMissing(missingObject, control, referenceDatabase, comparisonDatabase, chain); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/diff/UnexpectedIndexChangeGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.diff; 2 | 3 | import liquibase.change.Change; 4 | import liquibase.database.Database; 5 | import liquibase.diff.output.DiffOutputControl; 6 | import liquibase.diff.output.changelog.ChangeGeneratorChain; 7 | import liquibase.ext.hibernate.database.HibernateDatabase; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.Index; 10 | 11 | /** 12 | * Indexes tend to be added in the database that don't correspond to what is in Hibernate, so we suppress all dropIndex changes 13 | * based on indexes defined in the database but not in hibernate. 14 | */ 15 | public class UnexpectedIndexChangeGenerator extends liquibase.diff.output.changelog.core.UnexpectedIndexChangeGenerator { 16 | 17 | @Override 18 | public int getPriority(Class objectType, Database database) { 19 | if (Index.class.isAssignableFrom(objectType)) { 20 | return PRIORITY_ADDITIONAL; 21 | } 22 | return PRIORITY_NONE; 23 | } 24 | 25 | @Override 26 | public Change[] fixUnexpected(DatabaseObject unexpectedObject, DiffOutputControl control, Database referenceDatabase, Database comparisonDatabase, ChangeGeneratorChain chain) { 27 | if (referenceDatabase instanceof HibernateDatabase || comparisonDatabase instanceof HibernateDatabase) { 28 | return null; 29 | } else { 30 | return super.fixUnexpected(unexpectedObject, control, referenceDatabase, comparisonDatabase, chain); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/CatalogSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.exception.DatabaseException; 4 | import liquibase.snapshot.DatabaseSnapshot; 5 | import liquibase.snapshot.InvalidExampleException; 6 | import liquibase.snapshot.SnapshotGenerator; 7 | import liquibase.structure.DatabaseObject; 8 | import liquibase.structure.core.Catalog; 9 | 10 | 11 | /** 12 | * Hibernate doesn't really support Catalogs, so just return the passed example back as if it had all the info it needed. 13 | */ 14 | public class CatalogSnapshotGenerator extends HibernateSnapshotGenerator { 15 | 16 | public CatalogSnapshotGenerator() { 17 | super(Catalog.class); 18 | } 19 | 20 | @Override 21 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 22 | return new Catalog(snapshot.getDatabase().getDefaultCatalogName()).setDefault(true); 23 | } 24 | 25 | @Override 26 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 27 | // Nothing to add to 28 | } 29 | 30 | @Override 31 | public Class[] replaces() { 32 | return new Class[]{liquibase.snapshot.jvm.CatalogSnapshotGenerator.class}; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/ForeignKeySnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.diff.compare.DatabaseObjectComparatorFactory; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.ext.hibernate.database.HibernateDatabase; 6 | import liquibase.snapshot.DatabaseSnapshot; 7 | import liquibase.snapshot.InvalidExampleException; 8 | import liquibase.snapshot.SnapshotGenerator; 9 | import liquibase.structure.DatabaseObject; 10 | import liquibase.structure.core.ForeignKey; 11 | import liquibase.structure.core.Table; 12 | import org.hibernate.boot.spi.MetadataImplementor; 13 | 14 | import java.util.Collection; 15 | import java.util.Iterator; 16 | 17 | public class ForeignKeySnapshotGenerator extends HibernateSnapshotGenerator { 18 | 19 | public ForeignKeySnapshotGenerator() { 20 | super(ForeignKey.class, new Class[]{Table.class}); 21 | } 22 | 23 | @Override 24 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 25 | return example; 26 | } 27 | 28 | @Override 29 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 30 | if (!snapshot.getSnapshotControl().shouldInclude(ForeignKey.class)) { 31 | return; 32 | } 33 | if (foundObject instanceof Table) { 34 | Table table = (Table) foundObject; 35 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase(); 36 | MetadataImplementor metadata = (MetadataImplementor) database.getMetadata(); 37 | 38 | Collection tmapp = metadata.collectTableMappings(); 39 | Iterator tableMappings = tmapp.iterator(); 40 | while (tableMappings.hasNext()) { 41 | org.hibernate.mapping.Table hibernateTable = (org.hibernate.mapping.Table) tableMappings.next(); 42 | Iterator fkIterator = hibernateTable.getForeignKeyIterator(); 43 | while (fkIterator.hasNext()) { 44 | org.hibernate.mapping.ForeignKey hibernateForeignKey = (org.hibernate.mapping.ForeignKey) fkIterator.next(); 45 | Table currentTable = new Table().setName(hibernateTable.getName()); 46 | currentTable.setSchema(hibernateTable.getCatalog(), hibernateTable.getSchema()); 47 | 48 | org.hibernate.mapping.Table hibernateReferencedTable = hibernateForeignKey.getReferencedTable(); 49 | Table referencedTable = new Table().setName(hibernateReferencedTable.getName()); 50 | referencedTable.setSchema(hibernateReferencedTable.getCatalog(), hibernateReferencedTable.getSchema()); 51 | 52 | if (hibernateForeignKey.isCreationEnabled() && hibernateForeignKey.isPhysicalConstraint()) { 53 | ForeignKey fk = new ForeignKey(); 54 | fk.setName(hibernateForeignKey.getName()); 55 | fk.setPrimaryKeyTable(referencedTable); 56 | fk.setForeignKeyTable(currentTable); 57 | for (Object column : hibernateForeignKey.getColumns()) { 58 | fk.addForeignKeyColumn(new liquibase.structure.core.Column(((org.hibernate.mapping.Column) column).getName())); 59 | } 60 | for (Object column : hibernateForeignKey.getReferencedColumns()) { 61 | fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(((org.hibernate.mapping.Column) column).getName())); 62 | } 63 | if (fk.getPrimaryKeyColumns() == null || fk.getPrimaryKeyColumns().isEmpty()) { 64 | for (Object column : hibernateReferencedTable.getPrimaryKey().getColumns()) { 65 | fk.addPrimaryKeyColumn(new liquibase.structure.core.Column(((org.hibernate.mapping.Column) column).getName())); 66 | } 67 | } 68 | 69 | fk.setDeferrable(false); 70 | fk.setInitiallyDeferred(false); 71 | 72 | // Index index = new Index(); 73 | // index.setName("IX_" + fk.getName()); 74 | // index.setTable(fk.getForeignKeyTable()); 75 | // index.setColumns(fk.getForeignKeyColumns()); 76 | // fk.setBackingIndex(index); 77 | // table.getIndexes().add(index); 78 | 79 | if (DatabaseObjectComparatorFactory.getInstance().isSameObject(currentTable, table, null, database)) { 80 | table.getOutgoingForeignKeys().add(fk); 81 | table.getSchema().addDatabaseObject(fk); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | @Override 90 | public Class[] replaces() { 91 | return new Class[]{ liquibase.snapshot.jvm.ForeignKeySnapshotGenerator.class }; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/HibernateSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.database.Database; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.ext.hibernate.database.HibernateDatabase; 6 | import liquibase.logging.LogFactory; 7 | import liquibase.logging.LogService; 8 | import liquibase.logging.Logger; 9 | import liquibase.snapshot.DatabaseSnapshot; 10 | import liquibase.snapshot.InvalidExampleException; 11 | import liquibase.snapshot.SnapshotGenerator; 12 | import liquibase.snapshot.SnapshotGeneratorChain; 13 | import liquibase.structure.DatabaseObject; 14 | import org.hibernate.boot.spi.MetadataImplementor; 15 | import org.hibernate.mapping.Table; 16 | 17 | import java.util.Collection; 18 | import java.util.Iterator; 19 | 20 | /** 21 | * Base class for all Hibernate SnapshotGenerators 22 | */ 23 | public abstract class HibernateSnapshotGenerator implements SnapshotGenerator { 24 | 25 | private static final int PRIORITY_HIBERNATE_ADDITIONAL = 200; 26 | private static final int PRIORITY_HIBERNATE_DEFAULT = 100; 27 | 28 | private Class defaultFor = null; 29 | private Class[] addsTo = null; 30 | 31 | protected HibernateSnapshotGenerator(Class defaultFor) { 32 | this.defaultFor = defaultFor; 33 | } 34 | 35 | protected HibernateSnapshotGenerator(Class defaultFor, Class[] addsTo) { 36 | this.defaultFor = defaultFor; 37 | this.addsTo = addsTo; 38 | } 39 | 40 | @Override 41 | public Class[] replaces() { 42 | return null; 43 | } 44 | 45 | @Override 46 | public final int getPriority(Class objectType, Database database) { 47 | if (database instanceof HibernateDatabase) { 48 | if (defaultFor != null && defaultFor.isAssignableFrom(objectType)) { 49 | return PRIORITY_HIBERNATE_DEFAULT; 50 | } 51 | if (addsTo() != null) { 52 | for (Class type : addsTo()) { 53 | if (type.isAssignableFrom(objectType)) { 54 | return PRIORITY_HIBERNATE_ADDITIONAL; 55 | } 56 | } 57 | } 58 | } 59 | return PRIORITY_NONE; 60 | 61 | } 62 | 63 | @Override 64 | public final Class[] addsTo() { 65 | return addsTo; 66 | } 67 | 68 | @Override 69 | public final DatabaseObject snapshot(DatabaseObject example, DatabaseSnapshot snapshot, SnapshotGeneratorChain chain) throws DatabaseException, InvalidExampleException { 70 | if (defaultFor != null && defaultFor.isAssignableFrom(example.getClass())) { 71 | DatabaseObject result = snapshotObject(example, snapshot); 72 | return result; 73 | } 74 | DatabaseObject chainResponse = chain.snapshot(example, snapshot); 75 | if (chainResponse == null) { 76 | return null; 77 | } 78 | if (addsTo() != null) { 79 | for (Class addType : addsTo()) { 80 | if (addType.isAssignableFrom(example.getClass())) { 81 | if (chainResponse != null) { 82 | addTo(chainResponse, snapshot); 83 | } 84 | } 85 | } 86 | } 87 | return chainResponse; 88 | 89 | } 90 | 91 | protected abstract DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException; 92 | 93 | protected abstract void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException; 94 | 95 | protected org.hibernate.mapping.Table findHibernateTable(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException { 96 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase(); 97 | MetadataImplementor metadata = (MetadataImplementor) database.getMetadata(); 98 | 99 | Collection tmapp = metadata.collectTableMappings(); 100 | Iterator
tableMappings = tmapp.iterator(); 101 | 102 | while (tableMappings.hasNext()) { 103 | org.hibernate.mapping.Table hibernateTable = tableMappings.next(); 104 | if (hibernateTable.getName().equalsIgnoreCase(example.getName())) { 105 | return hibernateTable; 106 | } 107 | } 108 | return null; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/IndexSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.Scope; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.snapshot.DatabaseSnapshot; 6 | import liquibase.snapshot.InvalidExampleException; 7 | import liquibase.snapshot.SnapshotGenerator; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.*; 10 | 11 | public class IndexSnapshotGenerator extends HibernateSnapshotGenerator { 12 | 13 | private static final String HIBERNATE_ORDER_ASC = "asc"; 14 | private static final String HIBERNATE_ORDER_DESC = "desc"; 15 | 16 | @SuppressWarnings("unchecked") 17 | public IndexSnapshotGenerator() { 18 | super(Index.class, new Class[]{Table.class, ForeignKey.class, UniqueConstraint.class}); 19 | } 20 | 21 | @Override 22 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 23 | if (example.getSnapshotId() != null) { 24 | return example; 25 | } 26 | Relation table = ((Index) example).getRelation(); 27 | var hibernateTable = findHibernateTable(table, snapshot); 28 | if (hibernateTable == null) { 29 | return example; 30 | } 31 | for (var hibernateIndex : hibernateTable.getIndexes().values()) { 32 | Index index = handleHibernateIndex(table, hibernateIndex); 33 | if (index.getColumnNames().equalsIgnoreCase(((Index) example).getColumnNames())) { 34 | Scope.getCurrentScope().getLog(getClass()).info("Found index " + index.getName()); 35 | table.getIndexes().add(index); 36 | return index; 37 | } 38 | } 39 | return example; 40 | 41 | } 42 | 43 | @Override 44 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 45 | if (!snapshot.getSnapshotControl().shouldInclude(Index.class)) { 46 | return; 47 | } 48 | if (foundObject instanceof Table) { 49 | Table table = (Table) foundObject; 50 | org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot); 51 | if (hibernateTable == null) { 52 | return; 53 | } 54 | for (var hibernateIndex : hibernateTable.getIndexes().values()) { 55 | Index index = handleHibernateIndex(table, hibernateIndex); 56 | Scope.getCurrentScope().getLog(getClass()).info("Found index " + index.getName()); 57 | table.getIndexes().add(index); 58 | } 59 | } 60 | } 61 | 62 | private Index handleHibernateIndex(Relation table, org.hibernate.mapping.Index hibernateIndex) { 63 | Index index = new Index(); 64 | index.setRelation(table); 65 | index.setName(hibernateIndex.getName()); 66 | index.setUnique(isUniqueIndex(hibernateIndex)); 67 | for (var hibernateColumn : hibernateIndex.getColumns()) { 68 | String hibernateOrder = hibernateIndex.getColumnOrderMap().get(hibernateColumn); 69 | Boolean descending = HIBERNATE_ORDER_ASC.equals(hibernateOrder) 70 | ? Boolean.FALSE 71 | : (HIBERNATE_ORDER_DESC.equals(hibernateOrder) ? Boolean.TRUE : null); 72 | index.getColumns().add(new Column(hibernateColumn.getName()).setRelation(table).setDescending(descending)); 73 | } 74 | return index; 75 | } 76 | 77 | private Boolean isUniqueIndex(org.hibernate.mapping.Index hibernateIndex) { 78 | /* 79 | This seems to be necessary to explicitly tell liquibase that there's no 80 | actual diff in certain non-unique indexes 81 | */ 82 | if (hibernateIndex.getColumnSpan() == 1) { 83 | var col = hibernateIndex.getColumns().get(0); 84 | return col.isUnique(); 85 | } else { 86 | /* 87 | It seems that because Hibernate does not implement the unique property of the Jpa composite index, 88 | the diff command appears 'diffence', because the unique property of the entity index is 'null', 89 | and the value read from the database is 'false', resulting in the generated changeSet after the Drop and 90 | Recreate Index. 91 | */ 92 | return false; 93 | } 94 | } 95 | 96 | @Override 97 | public Class[] replaces() { 98 | return new Class[]{liquibase.snapshot.jvm.IndexSnapshotGenerator.class}; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/PrimaryKeySnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.Scope; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.snapshot.DatabaseSnapshot; 6 | import liquibase.snapshot.InvalidExampleException; 7 | import liquibase.snapshot.SnapshotGenerator; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.Column; 10 | import liquibase.structure.core.Index; 11 | import liquibase.structure.core.PrimaryKey; 12 | import liquibase.structure.core.Table; 13 | import org.hibernate.sql.Alias; 14 | 15 | public class PrimaryKeySnapshotGenerator extends HibernateSnapshotGenerator { 16 | 17 | private static final int PK_NAME_LENGTH = 63; 18 | private static final String PK_NAME_SUFFIX = "PK"; 19 | private static final Alias PK_NAME_ALIAS = new Alias(PK_NAME_LENGTH, PK_NAME_SUFFIX); 20 | 21 | public PrimaryKeySnapshotGenerator() { 22 | super(PrimaryKey.class, new Class[]{Table.class}); 23 | } 24 | 25 | @Override 26 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 27 | return example; 28 | } 29 | 30 | @Override 31 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 32 | if (!snapshot.getSnapshotControl().shouldInclude(PrimaryKey.class)) { 33 | return; 34 | } 35 | if (foundObject instanceof Table) { 36 | Table table = (Table) foundObject; 37 | org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot); 38 | if (hibernateTable == null) { 39 | return; 40 | } 41 | org.hibernate.mapping.PrimaryKey hibernatePrimaryKey = hibernateTable.getPrimaryKey(); 42 | if (hibernatePrimaryKey != null) { 43 | PrimaryKey pk = new PrimaryKey(); 44 | String hbnTableName = hibernateTable.getName(); 45 | 46 | String pkName = PK_NAME_ALIAS.toAliasString(hbnTableName); 47 | if (pkName.length() == PK_NAME_LENGTH) { 48 | String suffix = "_" + Integer.toHexString(hbnTableName.hashCode()).toUpperCase() + "_" + PK_NAME_SUFFIX; 49 | pkName = pkName.substring(0, PK_NAME_LENGTH - suffix.length()) + suffix; 50 | } 51 | pk.setName(pkName); 52 | 53 | pk.setTable(table); 54 | for (org.hibernate.mapping.Column hibernateColumn : hibernatePrimaryKey.getColumns()) { 55 | pk.getColumns().add(new Column(hibernateColumn.getName()).setRelation(table)); 56 | } 57 | 58 | Scope.getCurrentScope().getLog(getClass()).info("Found primary key " + pk.getName()); 59 | table.setPrimaryKey(pk); 60 | Index index = new Index(); 61 | index.setName("IX_" + pk.getName()); 62 | index.setRelation(table); 63 | index.setColumns(pk.getColumns()); 64 | index.setUnique(true); 65 | pk.setBackingIndex(index); 66 | table.getIndexes().add(index); 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public Class[] replaces() { 73 | return new Class[]{liquibase.snapshot.jvm.PrimaryKeySnapshotGenerator.class}; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/SchemaSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.exception.DatabaseException; 4 | import liquibase.snapshot.DatabaseSnapshot; 5 | import liquibase.snapshot.InvalidExampleException; 6 | import liquibase.snapshot.SnapshotGenerator; 7 | import liquibase.structure.DatabaseObject; 8 | import liquibase.structure.core.Catalog; 9 | import liquibase.structure.core.Schema; 10 | 11 | /** 12 | * Hibernate doesn't really support Schemas, so just return the passed example back as if it had all the info it needed. 13 | */ 14 | public class SchemaSnapshotGenerator extends HibernateSnapshotGenerator { 15 | 16 | public SchemaSnapshotGenerator() { 17 | super(Schema.class, new Class[]{Catalog.class}); 18 | } 19 | 20 | @Override 21 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 22 | return new Schema(snapshot.getDatabase().getDefaultCatalogName(), snapshot.getDatabase().getDefaultSchemaName()).setDefault(true); 23 | } 24 | 25 | @Override 26 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 27 | // Nothing to do 28 | } 29 | 30 | @Override 31 | public Class[] replaces() { 32 | return new Class[]{liquibase.snapshot.jvm.SchemaSnapshotGenerator.class}; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/SequenceSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.exception.DatabaseException; 4 | import liquibase.ext.hibernate.database.HibernateDatabase; 5 | import liquibase.snapshot.DatabaseSnapshot; 6 | import liquibase.snapshot.InvalidExampleException; 7 | import liquibase.snapshot.SnapshotGenerator; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.Schema; 10 | import liquibase.structure.core.Sequence; 11 | 12 | import java.math.BigInteger; 13 | 14 | /** 15 | * Sequence snapshots are not yet supported, but this class needs to be implemented in order to prevent the default SequenceSnapshotGenerator from running. 16 | */ 17 | public class SequenceSnapshotGenerator extends HibernateSnapshotGenerator { 18 | 19 | public SequenceSnapshotGenerator() { 20 | super(Sequence.class, new Class[]{Schema.class}); 21 | } 22 | 23 | @Override 24 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 25 | return example; 26 | } 27 | 28 | @Override 29 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 30 | if (!snapshot.getSnapshotControl().shouldInclude(Sequence.class)) { 31 | return; 32 | } 33 | 34 | if (foundObject instanceof Schema) { 35 | Schema schema = (Schema) foundObject; 36 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase(); 37 | for (org.hibernate.boot.model.relational.Namespace namespace : database.getMetadata().getDatabase().getNamespaces()) { 38 | for (org.hibernate.boot.model.relational.Sequence sequence : namespace.getSequences()) { 39 | schema.addDatabaseObject(new Sequence() 40 | .setName(sequence.getName().getSequenceName().getText()) 41 | .setSchema(schema) 42 | .setStartValue(BigInteger.valueOf(sequence.getInitialValue())) 43 | .setIncrementBy(BigInteger.valueOf(sequence.getIncrementSize())) 44 | ); 45 | } 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public Class[] replaces() { 52 | return new Class[]{ liquibase.snapshot.jvm.SequenceSnapshotGenerator.class }; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/TableSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.Scope; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.ext.hibernate.database.HibernateDatabase; 6 | import liquibase.ext.hibernate.snapshot.extension.ExtendedSnapshotGenerator; 7 | import liquibase.ext.hibernate.snapshot.extension.TableGeneratorSnapshotGenerator; 8 | import liquibase.snapshot.DatabaseSnapshot; 9 | import liquibase.snapshot.InvalidExampleException; 10 | import liquibase.snapshot.SnapshotGenerator; 11 | import liquibase.structure.DatabaseObject; 12 | import liquibase.structure.core.Schema; 13 | import liquibase.structure.core.Table; 14 | import org.hibernate.boot.spi.MetadataImplementor; 15 | import org.hibernate.generator.Generator; 16 | import org.hibernate.mapping.*; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | 23 | public class TableSnapshotGenerator extends HibernateSnapshotGenerator { 24 | 25 | private List> tableIdGenerators = new ArrayList<>(); 26 | 27 | public TableSnapshotGenerator() { 28 | super(Table.class, new Class[]{Schema.class}); 29 | tableIdGenerators.add(new TableGeneratorSnapshotGenerator()); 30 | } 31 | 32 | @Override 33 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 34 | if (example.getSnapshotId() != null) { 35 | return example; 36 | } 37 | org.hibernate.mapping.Table hibernateTable = findHibernateTable(example, snapshot); 38 | if (hibernateTable == null) { 39 | return example; 40 | } 41 | 42 | Table table = new Table().setName(hibernateTable.getName()); 43 | Scope.getCurrentScope().getLog(getClass()).info("Found table " + table.getName()); 44 | table.setSchema(example.getSchema()); 45 | if (hibernateTable.getComment() != null && !hibernateTable.getComment().isEmpty()) { 46 | table.setRemarks(hibernateTable.getComment()); 47 | } 48 | 49 | return table; 50 | } 51 | 52 | @Override 53 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 54 | if (!snapshot.getSnapshotControl().shouldInclude(Table.class)) { 55 | return; 56 | } 57 | 58 | if (foundObject instanceof Schema) { 59 | 60 | Schema schema = (Schema) foundObject; 61 | HibernateDatabase database = (HibernateDatabase) snapshot.getDatabase(); 62 | MetadataImplementor metadata = (MetadataImplementor) database.getMetadata(); 63 | 64 | Collection entityBindings = metadata.getEntityBindings(); 65 | Iterator tableMappings = entityBindings.iterator(); 66 | 67 | while (tableMappings.hasNext()) { 68 | PersistentClass pc = tableMappings.next(); 69 | 70 | org.hibernate.mapping.Table hibernateTable = pc.getTable(); 71 | if (hibernateTable.isPhysicalTable()) { 72 | addDatabaseObjectToSchema(hibernateTable, schema, snapshot); 73 | 74 | Collection joins = pc.getJoins(); 75 | Iterator joinMappings = joins.iterator(); 76 | while (joinMappings.hasNext()) { 77 | Join join = joinMappings.next(); 78 | addDatabaseObjectToSchema(join.getTable(), schema, snapshot); 79 | } 80 | } 81 | } 82 | 83 | Iterator classMappings = entityBindings.iterator(); 84 | while (classMappings.hasNext()) { 85 | PersistentClass persistentClass = classMappings.next(); 86 | if (!persistentClass.isInherited() && persistentClass.getIdentifier() instanceof SimpleValue) { 87 | var simpleValue = (SimpleValue) persistentClass.getIdentifier(); 88 | Generator ig = simpleValue.createGenerator( 89 | metadata.getMetadataBuildingOptions().getIdentifierGeneratorFactory(), 90 | database.getDialect(), 91 | (RootClass) persistentClass 92 | ); 93 | for (ExtendedSnapshotGenerator tableIdGenerator : tableIdGenerators) { 94 | if (tableIdGenerator.supports(ig)) { 95 | Table idTable = tableIdGenerator.snapshot(ig); 96 | idTable.setSchema(schema); 97 | schema.addDatabaseObject(snapshotObject(idTable, snapshot)); 98 | break; 99 | } 100 | } 101 | } 102 | } 103 | 104 | Collection collectionBindings = metadata.getCollectionBindings(); 105 | Iterator collIter = collectionBindings.iterator(); 106 | while (collIter.hasNext()) { 107 | org.hibernate.mapping.Collection coll = collIter.next(); 108 | org.hibernate.mapping.Table hTable = coll.getCollectionTable(); 109 | if (hTable.isPhysicalTable()) { 110 | addDatabaseObjectToSchema(hTable, schema, snapshot); 111 | } 112 | } 113 | } 114 | } 115 | 116 | private void addDatabaseObjectToSchema(org.hibernate.mapping.Table join, Schema schema, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 117 | Table joinTable = new Table().setName(join.getName()); 118 | joinTable.setSchema(schema); 119 | Scope.getCurrentScope().getLog(getClass()).info("Found table " + joinTable.getName()); 120 | schema.addDatabaseObject(snapshotObject(joinTable, snapshot)); 121 | } 122 | 123 | @Override 124 | public Class[] replaces() { 125 | return new Class[]{liquibase.snapshot.jvm.TableSnapshotGenerator.class}; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/UniqueConstraintSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.Scope; 4 | import liquibase.exception.DatabaseException; 5 | import liquibase.snapshot.DatabaseSnapshot; 6 | import liquibase.snapshot.InvalidExampleException; 7 | import liquibase.snapshot.SnapshotGenerator; 8 | import liquibase.structure.DatabaseObject; 9 | import liquibase.structure.core.Column; 10 | import liquibase.structure.core.Index; 11 | import liquibase.structure.core.Table; 12 | import liquibase.structure.core.UniqueConstraint; 13 | import liquibase.util.StringUtil; 14 | import org.hibernate.HibernateException; 15 | 16 | import java.math.BigInteger; 17 | import java.security.MessageDigest; 18 | import java.security.NoSuchAlgorithmException; 19 | 20 | public class UniqueConstraintSnapshotGenerator extends HibernateSnapshotGenerator { 21 | 22 | public UniqueConstraintSnapshotGenerator() { 23 | super(UniqueConstraint.class, new Class[]{Table.class}); 24 | } 25 | 26 | @Override 27 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 28 | return example; 29 | } 30 | 31 | @Override 32 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 33 | if (!snapshot.getSnapshotControl().shouldInclude(UniqueConstraint.class)) { 34 | return; 35 | } 36 | 37 | if (foundObject instanceof Table) { 38 | Table table = (Table) foundObject; 39 | org.hibernate.mapping.Table hibernateTable = findHibernateTable(table, snapshot); 40 | if (hibernateTable == null) { 41 | return; 42 | } 43 | for (var hibernateUnique : hibernateTable.getUniqueKeys().values()) { 44 | UniqueConstraint uniqueConstraint = new UniqueConstraint(); 45 | uniqueConstraint.setName(hibernateUnique.getName()); 46 | uniqueConstraint.setRelation(table); 47 | uniqueConstraint.setClustered(false); // No way to set true via Hibernate 48 | 49 | int i = 0; 50 | for (var hibernateColumn : hibernateUnique.getColumns()) { 51 | uniqueConstraint.addColumn(i++, new Column(hibernateColumn.getName()).setRelation(table)); 52 | } 53 | 54 | Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot); 55 | uniqueConstraint.setBackingIndex(index); 56 | 57 | Scope.getCurrentScope().getLog(getClass()).info("Found unique constraint " + uniqueConstraint); 58 | table.getUniqueConstraints().add(uniqueConstraint); 59 | } 60 | for (var column : hibernateTable.getColumns()) { 61 | if (column.isUnique()) { 62 | UniqueConstraint uniqueConstraint = new UniqueConstraint(); 63 | uniqueConstraint.setRelation(table); 64 | uniqueConstraint.setClustered(false); // No way to set true via Hibernate 65 | String name = "UC_" + table.getName().toUpperCase() + column.getName().toUpperCase() + "_COL"; 66 | if (name.length() > 64) { 67 | name = name.substring(0, 63); 68 | } 69 | uniqueConstraint.addColumn(0, new Column(column.getName()).setRelation(table)); 70 | uniqueConstraint.setName(name); 71 | Scope.getCurrentScope().getLog(getClass()).info("Found unique constraint " + uniqueConstraint); 72 | table.getUniqueConstraints().add(uniqueConstraint); 73 | 74 | Index index = getBackingIndex(uniqueConstraint, hibernateTable, snapshot); 75 | uniqueConstraint.setBackingIndex(index); 76 | 77 | } 78 | } 79 | 80 | for (UniqueConstraint uc : table.getUniqueConstraints()) { 81 | if (uc.getName() == null || uc.getName().isEmpty()) { 82 | String name = table.getName() + uc.getColumnNames(); 83 | name = "UCIDX" + hashedName(name); 84 | uc.setName(name); 85 | } 86 | } 87 | } 88 | } 89 | 90 | private String hashedName(String s) { 91 | try { 92 | MessageDigest md = MessageDigest.getInstance("MD5"); 93 | md.reset(); 94 | md.update(s.getBytes()); 95 | byte[] digest = md.digest(); 96 | BigInteger bigInt = new BigInteger(1, digest); 97 | // By converting to base 35 (full alphanumeric), we guarantee 98 | // that the length of the name will always be smaller than the 30 99 | // character identifier restriction enforced by a few dialects. 100 | return bigInt.toString(35); 101 | } catch (NoSuchAlgorithmException e) { 102 | throw new HibernateException("Unable to generate a hashed name!", e); 103 | } 104 | } 105 | 106 | protected Index getBackingIndex(UniqueConstraint uniqueConstraint, org.hibernate.mapping.Table hibernateTable, DatabaseSnapshot snapshot) { 107 | Index index = new Index(); 108 | index.setRelation(uniqueConstraint.getRelation()); 109 | index.setColumns(uniqueConstraint.getColumns()); 110 | index.setUnique(true); 111 | index.setName(String.format("%s_%s_IX",hibernateTable.getName(), StringUtil.randomIdentifer(4))); 112 | 113 | return index; 114 | } 115 | 116 | @Override 117 | public Class[] replaces() { 118 | return new Class[]{ liquibase.snapshot.jvm.UniqueConstraintSnapshotGenerator.class }; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/ViewSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.exception.DatabaseException; 4 | import liquibase.snapshot.DatabaseSnapshot; 5 | import liquibase.snapshot.InvalidExampleException; 6 | import liquibase.snapshot.SnapshotGenerator; 7 | import liquibase.structure.DatabaseObject; 8 | import liquibase.structure.core.Schema; 9 | import liquibase.structure.core.View; 10 | 11 | /** 12 | * View snapshots are not supported from hibernate, but this class needs to be implemented in order to prevent the default ViewSnapshotGenerator from running. 13 | */ 14 | public class ViewSnapshotGenerator extends HibernateSnapshotGenerator { 15 | 16 | public ViewSnapshotGenerator() { 17 | super(View.class, new Class[]{Schema.class}); 18 | } 19 | 20 | @Override 21 | protected DatabaseObject snapshotObject(DatabaseObject example, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 22 | throw new DatabaseException("No views in Hibernate mapping"); 23 | } 24 | 25 | @Override 26 | protected void addTo(DatabaseObject foundObject, DatabaseSnapshot snapshot) throws DatabaseException, InvalidExampleException { 27 | // No views in Hibernate mapping 28 | } 29 | 30 | @Override 31 | public Class[] replaces() { 32 | return new Class[]{ liquibase.snapshot.jvm.ViewSnapshotGenerator.class }; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/extension/ExtendedSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot.extension; 2 | 3 | public interface ExtendedSnapshotGenerator { 4 | 5 | U snapshot(T object); 6 | 7 | boolean supports(T object); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/liquibase/ext/hibernate/snapshot/extension/TableGeneratorSnapshotGenerator.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot.extension; 2 | 3 | import liquibase.structure.core.Column; 4 | import liquibase.structure.core.DataType; 5 | import liquibase.structure.core.PrimaryKey; 6 | import liquibase.structure.core.Table; 7 | import org.hibernate.generator.Generator; 8 | import org.hibernate.id.enhanced.TableGenerator; 9 | 10 | public class TableGeneratorSnapshotGenerator implements ExtendedSnapshotGenerator { 11 | 12 | private static final String PK_DATA_TYPE = "varchar"; 13 | private static final String VALUE_DATA_TYPE = "bigint"; 14 | 15 | @Override 16 | public Table snapshot(Generator ig) { 17 | TableGenerator tableGenerator = (TableGenerator) ig; 18 | Table table = new Table().setName(tableGenerator.getTableName()); 19 | 20 | Column pkColumn = new Column(); 21 | pkColumn.setName(tableGenerator.getSegmentColumnName()); 22 | DataType pkDataType = new DataType(PK_DATA_TYPE); 23 | pkDataType.setColumnSize(tableGenerator.getSegmentValueLength()); 24 | pkColumn.setType(pkDataType); 25 | pkColumn.setCertainDataType(false); 26 | pkColumn.setRelation(table); 27 | table.getColumns().add(pkColumn); 28 | 29 | PrimaryKey primaryKey = new PrimaryKey(); 30 | primaryKey.setName(tableGenerator.getTableName() + "PK"); 31 | primaryKey.addColumn(0, new Column(pkColumn.getName()).setRelation(table)); 32 | primaryKey.setTable(table); 33 | table.setPrimaryKey(primaryKey); 34 | 35 | Column valueColumn = new Column(); 36 | valueColumn.setName(tableGenerator.getValueColumnName()); 37 | valueColumn.setType(new DataType(VALUE_DATA_TYPE)); 38 | valueColumn.setNullable(false); 39 | valueColumn.setCertainDataType(false); 40 | valueColumn.setRelation(table); 41 | table.getColumns().add(valueColumn); 42 | 43 | return table; 44 | } 45 | 46 | @Override 47 | public boolean supports(Generator ig) { 48 | return ig instanceof TableGenerator; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/resources-filtered/build.properties: -------------------------------------------------------------------------------- 1 | build.${project.artifactId}.commit=${buildNumber} 2 | build.${project.artifactId}.branch=${scmBranch} 3 | build.${project.artifactId}.version=${pom.version} 4 | build.${project.artifactId}.timestamp=${maven.build.timestamp} 5 | build.${project.artifactId}.buildNumber=${ci.buildNumber} 6 | build.liquibase.version=${liquibase.version} -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/liquibase.database.Database: -------------------------------------------------------------------------------- 1 | liquibase.ext.hibernate.database.HibernateClassicDatabase 2 | liquibase.ext.hibernate.database.HibernateEjb3Database 3 | liquibase.ext.hibernate.database.HibernateSpringBeanDatabase 4 | liquibase.ext.hibernate.database.HibernateSpringPackageDatabase 5 | liquibase.ext.hibernate.database.JpaPersistenceDatabase 6 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/liquibase.diff.output.changelog.ChangeGenerator: -------------------------------------------------------------------------------- 1 | liquibase.ext.hibernate.diff.ChangedColumnChangeGenerator 2 | liquibase.ext.hibernate.diff.ChangedForeignKeyChangeGenerator 3 | liquibase.ext.hibernate.diff.ChangedPrimaryKeyChangeGenerator 4 | liquibase.ext.hibernate.diff.ChangedSequenceChangeGenerator 5 | liquibase.ext.hibernate.diff.ChangedUniqueConstraintChangeGenerator 6 | liquibase.ext.hibernate.diff.MissingSequenceChangeGenerator 7 | liquibase.ext.hibernate.diff.UnexpectedIndexChangeGenerator 8 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/liquibase.snapshot.SnapshotGenerator: -------------------------------------------------------------------------------- 1 | liquibase.ext.hibernate.snapshot.CatalogSnapshotGenerator 2 | liquibase.ext.hibernate.snapshot.ColumnSnapshotGenerator 3 | liquibase.ext.hibernate.snapshot.ForeignKeySnapshotGenerator 4 | liquibase.ext.hibernate.snapshot.IndexSnapshotGenerator 5 | liquibase.ext.hibernate.snapshot.PrimaryKeySnapshotGenerator 6 | liquibase.ext.hibernate.snapshot.SchemaSnapshotGenerator 7 | liquibase.ext.hibernate.snapshot.SequenceSnapshotGenerator 8 | liquibase.ext.hibernate.snapshot.TableSnapshotGenerator 9 | liquibase.ext.hibernate.snapshot.UniqueConstraintSnapshotGenerator 10 | liquibase.ext.hibernate.snapshot.ViewSnapshotGenerator 11 | -------------------------------------------------------------------------------- /src/test/groovy/HibernateDiffCommandTest.groovy: -------------------------------------------------------------------------------- 1 | import liquibase.harness.config.TestConfig 2 | import liquibase.harness.diff.DiffCommandTest 3 | 4 | class HibernateDiffCommandTest { //extends DiffCommandTest { 5 | // static { 6 | // TestConfig.instance.initDB = false 7 | // } 8 | } -------------------------------------------------------------------------------- /src/test/java/com/example/customconfig/auction/Item.java: -------------------------------------------------------------------------------- 1 | package com.example.customconfig.auction; 2 | 3 | import java.io.Serializable; 4 | 5 | import jakarta.persistence.Entity; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | 10 | @Entity 11 | public class Item implements Serializable { 12 | private static final long serialVersionUID = 1L; 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.AUTO) 16 | private Long id; 17 | 18 | private String name; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/AuctionInfo.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import java.util.Date; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.Entity; 7 | import jakarta.persistence.Id; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | @Getter 12 | @Setter 13 | @Entity 14 | public class AuctionInfo { 15 | @Id 16 | private String id; 17 | @Column(length = 1000) 18 | private String description; 19 | private Date ends; 20 | private Float maxAmount; 21 | 22 | public AuctionInfo(String id, String description, Date ends, Float maxAmount) { 23 | this.id = id; 24 | this.description = description; 25 | this.ends = ends; 26 | this.maxAmount = maxAmount; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/AuctionItem.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import jakarta.persistence.CascadeType; 7 | import jakarta.persistence.Column; 8 | import jakarta.persistence.Entity; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.OneToMany; 11 | import lombok.Getter; 12 | import lombok.Setter; 13 | 14 | @Entity 15 | public class AuctionItem extends Persistent { 16 | @Column(length = 1000) 17 | @Getter 18 | @Setter 19 | private String description; 20 | @Column(length = 200) 21 | @Getter 22 | @Setter 23 | private String shortDescription; 24 | @Setter 25 | private List bids; 26 | @Setter 27 | private Bid successfulBid; 28 | @Setter 29 | private User seller; 30 | @Getter 31 | @Setter 32 | private Date ends; 33 | @Getter 34 | @Setter 35 | private int condition; 36 | 37 | @OneToMany(mappedBy = "item", cascade = CascadeType.ALL) 38 | public List getBids() { 39 | return bids; 40 | } 41 | 42 | @ManyToOne 43 | public User getSeller() { 44 | return seller; 45 | } 46 | 47 | @ManyToOne 48 | public Bid getSuccessfulBid() { 49 | return successfulBid; 50 | } 51 | 52 | public String toString() { 53 | return shortDescription + " (" + description + ": " + condition 54 | + "/10)"; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/AuditedItem.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.hibernate.envers.Audited; 6 | 7 | import jakarta.persistence.Column; 8 | import jakarta.persistence.Entity; 9 | import jakarta.persistence.GeneratedValue; 10 | import jakarta.persistence.GenerationType; 11 | import jakarta.persistence.Id; 12 | import jakarta.persistence.SequenceGenerator; 13 | 14 | @Audited 15 | @Entity 16 | @Getter 17 | @Setter 18 | public class AuditedItem { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AUDITED_ITEM_SEQ") 22 | @SequenceGenerator(name = "AUDITED_ITEM_SEQ", sequenceName = "AUDITED_ITEM_SEQ") 23 | private long id; 24 | @Column(unique = true) 25 | private String name; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/Bid.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import java.util.Date; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.hibernate.annotations.Cache; 8 | import org.hibernate.annotations.CacheConcurrencyStrategy; 9 | 10 | import jakarta.persistence.Column; 11 | import jakarta.persistence.DiscriminatorValue; 12 | import jakarta.persistence.Entity; 13 | import jakarta.persistence.Inheritance; 14 | import jakarta.persistence.InheritanceType; 15 | import jakarta.persistence.ManyToOne; 16 | import jakarta.persistence.Transient; 17 | 18 | @Entity 19 | @Inheritance(strategy = InheritanceType.SINGLE_TABLE) 20 | @DiscriminatorValue("Y") 21 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 22 | public class Bid extends Persistent { 23 | @Setter 24 | private AuctionItem item; 25 | @Setter 26 | @Getter 27 | private float amount; 28 | @Setter 29 | private Date datetime; 30 | @Setter 31 | private User bidder; 32 | 33 | @ManyToOne 34 | public AuctionItem getItem() { 35 | return item; 36 | } 37 | 38 | @Column(nullable = false, name = "datetime") 39 | public Date getDatetime() { 40 | return datetime; 41 | } 42 | 43 | @ManyToOne(optional = false) 44 | public User getBidder() { 45 | return bidder; 46 | } 47 | 48 | public String toString() { 49 | return bidder.getUserName() + " $" + amount; 50 | } 51 | 52 | @Transient 53 | public boolean isBuyNow() { 54 | return false; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/BuyNow.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.Transient; 5 | 6 | @Entity 7 | public class BuyNow extends Bid { 8 | 9 | @Transient 10 | public boolean isBuyNow() { 11 | return true; 12 | } 13 | 14 | public String toString() { 15 | return super.toString() + " (buy now)"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/FirstTable.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Setter 8 | @Getter 9 | @Entity 10 | @SecondaryTable(name = "second_table", pkJoinColumns = @PrimaryKeyJoinColumn(name = "first_table_id")) 11 | public class FirstTable { 12 | @Id 13 | private Long id; 14 | 15 | @Column(name = "name") 16 | private String name; 17 | 18 | @Embedded 19 | private SecondTable secondTable; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/Item.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.SequenceGenerator; 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | 12 | @Setter 13 | @Getter 14 | @Entity 15 | public class Item { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ITEM_SEQ") 19 | @SequenceGenerator(name = "ITEM_SEQ", sequenceName = "ITEM_SEQ", initialValue = 1000, allocationSize = 100) 20 | private long id; 21 | @Column(unique = true) 22 | private String name; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/Name.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.Embeddable; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Setter 8 | @Getter 9 | @Embeddable 10 | public class Name { 11 | private String firstName; 12 | private String lastName; 13 | private Character initial; 14 | 15 | public Name(String first, Character middle, String last) { 16 | firstName = first; 17 | initial = middle; 18 | lastName = last; 19 | } 20 | 21 | public String toString() { 22 | StringBuffer buf = new StringBuffer().append(firstName).append(' '); 23 | if (initial != null) 24 | buf.append(initial).append(' '); 25 | return buf.append(lastName).toString(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/Persistent.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.GeneratedValue; 4 | import jakarta.persistence.Id; 5 | import jakarta.persistence.MappedSuperclass; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @MappedSuperclass 10 | public class Persistent { 11 | @Setter 12 | private Long id; 13 | 14 | @Id 15 | @GeneratedValue 16 | public Long getId() { 17 | return id; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/SecondTable.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Embeddable; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | @Embeddable 9 | public class SecondTable { 10 | 11 | @Column(table = "second_table") 12 | @Getter 13 | @Setter 14 | private String secondName; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/User.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import java.util.List; 4 | 5 | import jakarta.persistence.CascadeType; 6 | import jakarta.persistence.Entity; 7 | import jakarta.persistence.OneToMany; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | @Setter 12 | @Entity 13 | public class User extends Persistent { 14 | @Getter 15 | private String userName; 16 | @Getter 17 | private String password; 18 | @Getter 19 | private String email; 20 | @Getter 21 | private Name name; 22 | private List bids; 23 | private List auctions; 24 | 25 | @OneToMany(mappedBy = "seller", cascade = CascadeType.ALL) 26 | public List getAuctions() { 27 | return auctions; 28 | } 29 | 30 | @OneToMany(mappedBy = "bidder", cascade = CascadeType.ALL) 31 | public List getBids() { 32 | return bids; 33 | } 34 | 35 | public String toString() { 36 | return userName; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/example/ejb3/auction/Watcher.java: -------------------------------------------------------------------------------- 1 | package com.example.ejb3.auction; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import jakarta.persistence.ManyToOne; 8 | import jakarta.persistence.TableGenerator; 9 | 10 | @Entity 11 | public class Watcher { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.TABLE, generator = "WATCHER_SEQ") 15 | @TableGenerator(name = "WATCHER_SEQ", table = "WatcherSeqTable") 16 | private Integer id; 17 | 18 | @SuppressWarnings("unused") 19 | private String name; 20 | 21 | @ManyToOne 22 | private AuctionItem auctionItem; 23 | } -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/AuctionInfo.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Date; 6 | 7 | @Getter 8 | public class AuctionInfo { 9 | private long id; 10 | private String description; 11 | private Date ends; 12 | private Float maxAmount; 13 | 14 | public AuctionInfo(long id, String description, Date ends, Float maxAmount) { 15 | this.id = id; 16 | this.description = description; 17 | this.ends = ends; 18 | this.maxAmount = maxAmount; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/AuctionItem.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | @Getter 10 | @Setter 11 | public class AuctionItem extends Persistent { 12 | private String description; 13 | private String shortDescription; 14 | private List bids; 15 | private Bid successfulBid; 16 | private User seller; 17 | private Date ends; 18 | private int condition; 19 | 20 | public String toString() { 21 | return shortDescription + " (" + description + ": " + condition + "/10)"; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/Bid.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.Date; 7 | 8 | @Getter 9 | @Setter 10 | public class Bid extends Persistent { 11 | private AuctionItem item; 12 | private float amount; 13 | private Date datetime; 14 | private User bidder; 15 | 16 | public String toString() { 17 | return bidder.getUserName() + " $" + amount; 18 | } 19 | 20 | public boolean isBuyNow() { 21 | return false; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/BuyNow.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | public class BuyNow extends Bid { 4 | public boolean isBuyNow() { 5 | return true; 6 | } 7 | 8 | public String toString() { 9 | return super.toString() + " (buy now)"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/Name.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Name { 9 | private String firstName; 10 | private String lastName; 11 | private Character initial; 12 | 13 | public Name(String first, Character middle, String last) { 14 | firstName = first; 15 | initial = middle; 16 | lastName = last; 17 | } 18 | 19 | public String toString() { 20 | StringBuffer buf = new StringBuffer().append(firstName).append(' '); 21 | if (initial != null) 22 | buf.append(initial).append(' '); 23 | return buf.append(lastName).toString(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/Persistent.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | public class Persistent { 7 | @Setter 8 | @Getter 9 | private Long id; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/User.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.List; 7 | 8 | @Getter 9 | @Setter 10 | public class User extends Persistent { 11 | private String userName; 12 | private String password; 13 | private String email; 14 | private Name name; 15 | private List bids; 16 | private List auctions; 17 | 18 | public String toString() { 19 | return userName; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/com/example/pojo/auction/Watcher.java: -------------------------------------------------------------------------------- 1 | package com.example.pojo.auction; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.Id; 5 | import jakarta.persistence.ManyToOne; 6 | 7 | @Entity 8 | public class Watcher { 9 | 10 | @Id 11 | private Integer id; 12 | 13 | @SuppressWarnings("unused") 14 | private String name; 15 | 16 | @ManyToOne 17 | private AuctionItem auctionItem; 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/com/example/timezone/Item.java: -------------------------------------------------------------------------------- 1 | package com.example.timezone; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.time.Instant; 8 | import java.time.LocalDateTime; 9 | 10 | @Getter 11 | @Setter 12 | @Entity 13 | public class Item { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.SEQUENCE) 17 | private long id; 18 | 19 | @Column 20 | private Instant timestamp1; 21 | 22 | @Column 23 | private LocalDateTime timestamp2; 24 | 25 | @Column(columnDefinition = "timestamp") 26 | private Instant timestamp3; 27 | 28 | @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE") 29 | private LocalDateTime timestamp4; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/HibernateClassicDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.CatalogAndSchema; 4 | import liquibase.database.Database; 5 | import liquibase.database.DatabaseConnection; 6 | import liquibase.integration.commandline.CommandLineUtils; 7 | import liquibase.resource.ClassLoaderResourceAccessor; 8 | import liquibase.snapshot.DatabaseSnapshot; 9 | import liquibase.snapshot.SnapshotControl; 10 | import liquibase.snapshot.SnapshotGeneratorFactory; 11 | import liquibase.structure.core.Schema; 12 | import liquibase.structure.core.Table; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | import static org.hamcrest.Matchers.*; 19 | import static org.hamcrest.core.AllOf.allOf; 20 | import static org.junit.Assert.*; 21 | 22 | 23 | public class HibernateClassicDatabaseTest { 24 | 25 | private static final String CUSTOMCONFIG_CLASS = "com.example.customconfig.CustomClassicConfigurationFactoryImpl"; 26 | 27 | private DatabaseConnection conn; 28 | private HibernateClassicDatabase db; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | db = new HibernateClassicDatabase(); 33 | } 34 | 35 | @After 36 | public void tearDown() throws Exception { 37 | db.close(); 38 | } 39 | 40 | // @Test 41 | // public void runMain() throws Exception { 42 | // Main.main(new String[]{ 43 | // "--url=hibernate:classic:com/example/pojo/Hibernate.cfg.xml", 44 | // "--referenceUrl=jdbc:mysql://vagrant/lbcat", "--referenceUsername=lbuser", 45 | // "--referencePassword=lbuser", 46 | // "--logLevel=debug", 47 | // "diffChangeLog" 48 | // }); 49 | // } 50 | 51 | // @Test 52 | // public void testHibernateUrlSimple() throws DatabaseException { 53 | // conn = new JdbcConnection(new HibernateConnection("hibernate:classic:com/example/pojo/Hibernate.cfg.xml")); 54 | // db.setConnection(conn); 55 | // assertNotNull(db.getConfiguration().getClassMapping(AuctionItem.class.getName())); 56 | // assertNotNull(db.getConfiguration().getClassMapping(Watcher.class.getName())); 57 | // } 58 | // 59 | // 60 | // @Test 61 | // public void testCustomConfigMustHaveItemClassMapping() throws DatabaseException { 62 | // conn = new JdbcConnection(new HibernateConnection("hibernate:classic:" + CUSTOMCONFIG_CLASS)); 63 | // db.setConnection(conn); 64 | // assertNotNull(db.getConfiguration().getClassMapping(Item.class.getName())); 65 | // } 66 | 67 | @Test 68 | public void simpleHibernateUrl() throws Exception { 69 | String url = "hibernate:classic:com/example/pojo/Hibernate.cfg.xml"; 70 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 71 | 72 | assertNotNull(database); 73 | 74 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 75 | 76 | assertPojoHibernateMapped(snapshot); 77 | } 78 | 79 | @Test 80 | public void nationalizedCharactersHibernateUrl() throws Exception { 81 | String url = "hibernate:classic:com/example/pojo/Hibernate.cfg.xml?hibernate.use_nationalized_character_data=true"; 82 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 83 | 84 | assertNotNull(database); 85 | 86 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 87 | 88 | assertPojoHibernateMapped(snapshot); 89 | Table watcherTable = (Table) snapshot.get(new Table().setName("watcher").setSchema(new Schema())); 90 | assertEquals("varchar", watcherTable.getColumn("name").getType().getTypeName()); 91 | } 92 | 93 | public static void assertPojoHibernateMapped(DatabaseSnapshot snapshot) { 94 | assertThat(snapshot.get(Table.class), containsInAnyOrder( 95 | hasProperty("name", is("Bid")), 96 | hasProperty("name", is("Watcher")), 97 | hasProperty("name", is("AuctionUser")), 98 | hasProperty("name", is("AuctionItem")))); 99 | 100 | 101 | Table bidTable = (Table) snapshot.get(new Table().setName("bid").setSchema(new Schema())); 102 | Table auctionItemTable = (Table) snapshot.get(new Table().setName("auctionitem").setSchema(new Schema())); 103 | 104 | assertTrue(bidTable.getColumn("id").isAutoIncrement()); 105 | assertFalse(bidTable.getColumn("isBuyNow").isAutoIncrement()); 106 | assertEquals("Y if a \"buy now\", N if a regular bid.", bidTable.getColumn("isBuyNow").getRemarks()); 107 | assertFalse(bidTable.getColumn("datetime").isNullable()); 108 | assertTrue(auctionItemTable.getColumn("condition").isNullable()); 109 | 110 | assertThat(bidTable.getColumns(), containsInAnyOrder( 111 | hasProperty("name", is("id")), 112 | hasProperty("name", is("isBuyNow")), 113 | hasProperty("name", is("item")), 114 | hasProperty("name", is("amount")), 115 | hasProperty("name", is("datetime")), 116 | hasProperty("name", is("bidder")) 117 | )); 118 | 119 | assertThat(bidTable.getPrimaryKey().getColumnNames(), is("id")); 120 | 121 | assertThat(bidTable.getOutgoingForeignKeys(), containsInAnyOrder( 122 | allOf( 123 | hasProperty("primaryKeyColumns", hasToString("[HIBERNATE.AuctionItem.id]")), 124 | hasProperty("foreignKeyColumns", hasToString("[HIBERNATE.Bid.item]")), 125 | hasProperty("primaryKeyTable", hasProperty("name", is("AuctionItem"))) 126 | ), 127 | allOf( 128 | hasProperty("primaryKeyColumns", hasToString("[HIBERNATE.AuctionUser.id]")), 129 | hasProperty("foreignKeyColumns", hasToString("[HIBERNATE.Bid.bidder]")), 130 | hasProperty("primaryKeyTable", hasProperty("name", is("AuctionUser"))) 131 | ) 132 | )); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/HibernateDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.database.DatabaseFactory; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.*; 7 | 8 | public class HibernateDatabaseTest { 9 | 10 | @Test 11 | public void getDefaultDriver() { 12 | assertEquals("liquibase.ext.hibernate.database.connection.HibernateDriver", DatabaseFactory.getInstance().findDefaultDriver("hibernate:ejb3:pers")); 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/HibernateEjb3DatabaseTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.CatalogAndSchema; 4 | import liquibase.database.Database; 5 | import liquibase.integration.commandline.CommandLineUtils; 6 | import liquibase.resource.ClassLoaderResourceAccessor; 7 | import liquibase.snapshot.DatabaseSnapshot; 8 | import liquibase.snapshot.SnapshotControl; 9 | import liquibase.snapshot.SnapshotGeneratorFactory; 10 | import liquibase.structure.core.Schema; 11 | import liquibase.structure.core.Table; 12 | import org.junit.Test; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.*; 16 | import static org.hamcrest.core.AllOf.allOf; 17 | import static org.junit.Assert.*; 18 | 19 | public class HibernateEjb3DatabaseTest { 20 | 21 | @Test 22 | public void simpleEjb3Url() throws Exception { 23 | String url = "hibernate:ejb3:auction"; 24 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 25 | 26 | assertNotNull(database); 27 | 28 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 29 | 30 | assertEjb3HibernateMapped(snapshot); 31 | } 32 | 33 | @Test 34 | public void nationalizedCharactersEjb3Url() throws Exception { 35 | String url = "hibernate:ejb3:auction?hibernate.use_nationalized_character_data=true"; 36 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 37 | 38 | assertNotNull(database); 39 | 40 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 41 | 42 | assertEjb3HibernateMapped(snapshot); 43 | Table userTable = (Table) snapshot.get(new Table().setName("user").setSchema(new Schema())); 44 | assertEquals("nvarchar", userTable.getColumn("userName").getType().getTypeName()); 45 | } 46 | 47 | public static void assertEjb3HibernateMapped(DatabaseSnapshot snapshot) { 48 | assertThat(snapshot.get(Table.class), containsInAnyOrder( 49 | hasProperty("name", is("Bid")), 50 | hasProperty("name", is("Watcher")), 51 | hasProperty("name", is("User")), 52 | hasProperty("name", is("AuctionInfo")), 53 | hasProperty("name", is("AuctionItem")), 54 | hasProperty("name", is("Item")), 55 | hasProperty("name", is("AuditedItem")), 56 | hasProperty("name", is("AuditedItem_AUD")), 57 | hasProperty("name", is("REVINFO")), 58 | hasProperty("name", is("WatcherSeqTable")), 59 | hasProperty("name", is("FirstTable")), 60 | hasProperty("name", is("second_table")))); 61 | 62 | 63 | Table bidTable = (Table) snapshot.get(new Table().setName("bid").setSchema(new Schema())); 64 | Table auctionInfoTable = (Table) snapshot.get(new Table().setName("auctioninfo").setSchema(new Schema())); 65 | Table auctionItemTable = (Table) snapshot.get(new Table().setName("auctionitem").setSchema(new Schema())); 66 | 67 | assertThat(bidTable.getColumns(), containsInAnyOrder( 68 | hasProperty("name", is("id")), 69 | hasProperty("name", is("item_id")), 70 | hasProperty("name", is("amount")), 71 | hasProperty("name", is("datetime")), 72 | hasProperty("name", is("bidder_id")), 73 | hasProperty("name", is("DTYPE")) 74 | )); 75 | 76 | assertTrue(bidTable.getColumn("id").isAutoIncrement()); 77 | assertFalse(auctionInfoTable.getColumn("id").isAutoIncrement()); 78 | assertFalse(bidTable.getColumn("datetime").isNullable()); 79 | assertTrue(auctionItemTable.getColumn("ends").isNullable()); 80 | 81 | assertThat(bidTable.getPrimaryKey().getColumnNames(), is("id")); 82 | 83 | assertThat(bidTable.getOutgoingForeignKeys(), containsInAnyOrder( 84 | allOf( 85 | hasProperty("primaryKeyColumns", hasToString("[HIBERNATE.AuctionItem.id]")), 86 | hasProperty("foreignKeyColumns", hasToString("[HIBERNATE.Bid.item_id]")), 87 | hasProperty("primaryKeyTable", hasProperty("name", is("AuctionItem"))) 88 | ), 89 | allOf( 90 | hasProperty("primaryKeyColumns", hasToString("[HIBERNATE.User.id]")), 91 | hasProperty("foreignKeyColumns", hasToString("[HIBERNATE.Bid.bidder_id]")), 92 | hasProperty("primaryKeyTable", hasProperty("name", is("User"))) 93 | ) 94 | )); 95 | 96 | Table secondTable = (Table) snapshot.get(new Table().setName("second_table").setSchema(new Schema())); 97 | assertThat(secondTable.getColumns(), containsInAnyOrder( 98 | hasProperty("name", is("first_table_id")), 99 | hasProperty("name", is("secondName")) 100 | )); 101 | assertThat(secondTable.getPrimaryKey().getColumnNames(), is("first_table_id")); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/HibernateSpringDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.CatalogAndSchema; 4 | import liquibase.database.Database; 5 | import liquibase.database.DatabaseConnection; 6 | import liquibase.database.jvm.JdbcConnection; 7 | import liquibase.exception.DatabaseException; 8 | import liquibase.ext.hibernate.database.connection.HibernateConnection; 9 | import liquibase.integration.commandline.CommandLineUtils; 10 | import liquibase.resource.ClassLoaderResourceAccessor; 11 | import liquibase.snapshot.DatabaseSnapshot; 12 | import liquibase.snapshot.SnapshotControl; 13 | import liquibase.snapshot.SnapshotGeneratorFactory; 14 | import liquibase.structure.core.Schema; 15 | import liquibase.structure.core.Table; 16 | import org.hibernate.dialect.H2Dialect; 17 | import org.junit.After; 18 | import org.junit.Test; 19 | 20 | import com.example.ejb3.auction.Bid; 21 | import com.example.ejb3.auction.BuyNow; 22 | import com.example.pojo.auction.AuctionItem; 23 | import com.example.pojo.auction.Watcher; 24 | 25 | import static org.junit.Assert.*; 26 | 27 | public class HibernateSpringDatabaseTest { 28 | 29 | private DatabaseConnection conn; 30 | private HibernateDatabase db; 31 | 32 | @After 33 | public void tearDown() throws Exception { 34 | if (db != null) { 35 | db.close(); 36 | } 37 | } 38 | 39 | @Test 40 | public void testSpringUrlSimple() throws DatabaseException { 41 | conn = new JdbcConnection(new HibernateConnection("hibernate:spring:spring.ctx.xml?bean=sessionFactory", new ClassLoaderResourceAccessor())); 42 | db = new HibernateSpringBeanDatabase(); 43 | db.setConnection(conn); 44 | assertNotNull(db.getMetadata().getEntityBinding(AuctionItem.class.getName())); 45 | assertNotNull(db.getMetadata().getEntityBinding(Watcher.class.getName())); 46 | } 47 | 48 | 49 | @Test 50 | public void testSpringPackageScanningMustHaveItemClassMapping() throws DatabaseException { 51 | conn = new JdbcConnection(new HibernateConnection("hibernate:spring:com.example.ejb3.auction?dialect=" + H2Dialect.class.getName(), new ClassLoaderResourceAccessor())); 52 | db = new HibernateSpringPackageDatabase(); 53 | db.setConnection(conn); 54 | assertNotNull(db.getMetadata().getEntityBinding(Bid.class.getName())); 55 | assertNotNull(db.getMetadata().getEntityBinding(BuyNow.class.getName())); 56 | } 57 | 58 | @Test 59 | public void simpleSpringUrl() throws Exception { 60 | String url = "hibernate:spring:spring.ctx.xml?bean=sessionFactory"; 61 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 62 | 63 | assertNotNull(database); 64 | 65 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 66 | 67 | HibernateClassicDatabaseTest.assertPojoHibernateMapped(snapshot); 68 | } 69 | 70 | @Test 71 | public void nationalizedCharactersSpringBeanUrl() throws Exception { 72 | String url = "hibernate:spring:spring.ctx.xml?hibernate.use_nationalized_character_data=true&bean=sessionFactory"; 73 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 74 | 75 | assertNotNull(database); 76 | 77 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 78 | 79 | HibernateClassicDatabaseTest.assertPojoHibernateMapped(snapshot); 80 | Table watcherTable = (Table) snapshot.get(new Table().setName("watcher").setSchema(new Schema())); 81 | assertEquals("nvarchar", watcherTable.getColumn("name").getType().getTypeName()); 82 | } 83 | 84 | @Test 85 | public void simpleSpringScanningUrl() throws Exception { 86 | String url = "hibernate:spring:com.example.ejb3.auction?dialect=" + H2Dialect.class.getName(); 87 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 88 | 89 | assertNotNull(database); 90 | 91 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 92 | 93 | HibernateEjb3DatabaseTest.assertEjb3HibernateMapped(snapshot); 94 | } 95 | 96 | @Test 97 | public void nationalizedCharactersSpringScanningUrl() throws Exception { 98 | String url = "hibernate:spring:com.example.ejb3.auction?hibernate.use_nationalized_character_data=true&dialect=" + H2Dialect.class.getName(); 99 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 100 | 101 | assertNotNull(database); 102 | 103 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 104 | 105 | HibernateEjb3DatabaseTest.assertEjb3HibernateMapped(snapshot); 106 | Table userTable = (Table) snapshot.get(new Table().setName("user").setSchema(new Schema())); 107 | assertEquals("varchar", userTable.getColumn("userName").getType().getTypeName()); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/JPAPersistenceDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database; 2 | 3 | import liquibase.CatalogAndSchema; 4 | import liquibase.database.Database; 5 | import liquibase.integration.commandline.CommandLineUtils; 6 | import liquibase.resource.ClassLoaderResourceAccessor; 7 | import liquibase.snapshot.DatabaseSnapshot; 8 | import liquibase.snapshot.SnapshotControl; 9 | import liquibase.snapshot.SnapshotGeneratorFactory; 10 | import org.junit.Test; 11 | 12 | import static org.junit.Assert.*; 13 | 14 | /** 15 | * @author Mårten Svantesson 16 | */ 17 | public class JPAPersistenceDatabaseTest { 18 | @Test 19 | public void persistenceXML() throws Exception { 20 | String url = "jpa:persistence:META-INF/persistence.xml"; 21 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), url, null, null, null, null, null, false, false, null, null, null, null, null, null, null); 22 | 23 | assertNotNull(database); 24 | 25 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 26 | 27 | HibernateEjb3DatabaseTest.assertEjb3HibernateMapped(snapshot); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/database/connection/HibernateConnectionTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.database.connection; 2 | 3 | import liquibase.resource.ClassLoaderResourceAccessor; 4 | import org.junit.After; 5 | import org.junit.Assert; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | 11 | public class HibernateConnectionTest { 12 | 13 | private final String FILE_PATH = "/path/to/file.ext"; 14 | 15 | @Before 16 | public void setUp() throws Exception { 17 | 18 | } 19 | 20 | @After 21 | public void tearDown() throws Exception { 22 | 23 | } 24 | 25 | @Test 26 | public void testHibernateUrlSimple() { 27 | HibernateConnection conn = new HibernateConnection("hibernate:classic:" + FILE_PATH, new ClassLoaderResourceAccessor()); 28 | Assert.assertEquals("hibernate:classic", conn.getPrefix()); 29 | assertEquals(FILE_PATH, conn.getPath()); 30 | assertEquals(0, conn.getProperties().size()); 31 | } 32 | 33 | @Test 34 | public void testHibernateUrlWithProperties() { 35 | HibernateConnection conn = new HibernateConnection("hibernate:classic:" + FILE_PATH + "?foo=bar&name=John+Doe", new ClassLoaderResourceAccessor()); 36 | assertEquals("hibernate:classic", conn.getPrefix()); 37 | assertEquals(FILE_PATH, conn.getPath()); 38 | assertEquals(2, conn.getProperties().size()); 39 | assertEquals("bar", conn.getProperties().getProperty("foo", null)); 40 | assertEquals("John Doe", conn.getProperties().getProperty("name", null)); 41 | } 42 | 43 | @Test 44 | public void testEjb3UrlSimple() { 45 | HibernateConnection conn = new HibernateConnection("hibernate:ejb3:" + FILE_PATH, new ClassLoaderResourceAccessor()); 46 | assertEquals("hibernate:ejb3", conn.getPrefix()); 47 | assertEquals(FILE_PATH, conn.getPath()); 48 | assertEquals(0, conn.getProperties().size()); 49 | } 50 | 51 | @Test 52 | public void testEjb3UrlWithProperties() { 53 | HibernateConnection conn = new HibernateConnection("hibernate:ejb3:" + FILE_PATH + "?foo=bar&name=John+Doe", new ClassLoaderResourceAccessor()); 54 | assertEquals("hibernate:ejb3", conn.getPrefix()); 55 | assertEquals(FILE_PATH, conn.getPath()); 56 | assertEquals(2, conn.getProperties().size()); 57 | assertEquals("bar", conn.getProperties().getProperty("foo", null)); 58 | assertEquals("John Doe", conn.getProperties().getProperty("name", null)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/snapshot/ColumnSnapshotGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.exception.DatabaseException; 4 | import liquibase.structure.core.DataType; 5 | import org.hibernate.type.SqlTypes; 6 | import org.junit.Test; 7 | 8 | import java.sql.Types; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | public class ColumnSnapshotGeneratorTest { 13 | 14 | @Test 15 | public void toDataType() throws DatabaseException { 16 | ColumnSnapshotGenerator columnSnapshotGenerator = new ColumnSnapshotGenerator(); 17 | DataType varchar = columnSnapshotGenerator.toDataType("varchar(255)", Types.VARCHAR); 18 | assertEquals("varchar", varchar.getTypeName()); 19 | assertEquals(255, varchar.getColumnSize().intValue()); 20 | assertEquals(Types.VARCHAR, varchar.getDataTypeId().intValue()); 21 | assertNull(varchar.getColumnSizeUnit()); 22 | 23 | DataType intType = columnSnapshotGenerator.toDataType("integer", Types.INTEGER); 24 | assertEquals("integer", intType.getTypeName()); 25 | 26 | DataType varcharChar = columnSnapshotGenerator.toDataType("varchar2(30 char)", Types.INTEGER); 27 | assertEquals("varchar2", varcharChar.getTypeName()); 28 | assertEquals(30, varcharChar.getColumnSize().intValue()); 29 | assertEquals(DataType.ColumnSizeUnit.CHAR, varcharChar.getColumnSizeUnit()); 30 | 31 | 32 | DataType enumType = columnSnapshotGenerator.toDataType("enum ('a', 'b', 'c')", SqlTypes.ENUM); 33 | assertEquals("enum ('a', 'b', 'c')", enumType.getTypeName()); 34 | assertNull(enumType.getColumnSize()); 35 | assertEquals(SqlTypes.ENUM, enumType.getDataTypeId().intValue()); 36 | assertNull(enumType.getColumnSizeUnit()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/liquibase/ext/hibernate/snapshot/TimezoneSnapshotTest.java: -------------------------------------------------------------------------------- 1 | package liquibase.ext.hibernate.snapshot; 2 | 3 | import liquibase.CatalogAndSchema; 4 | import liquibase.database.Database; 5 | import liquibase.integration.commandline.CommandLineUtils; 6 | import liquibase.resource.ClassLoaderResourceAccessor; 7 | import liquibase.snapshot.DatabaseSnapshot; 8 | import liquibase.snapshot.SnapshotControl; 9 | import liquibase.snapshot.SnapshotGeneratorFactory; 10 | import liquibase.structure.DatabaseObject; 11 | import liquibase.structure.core.Column; 12 | import liquibase.structure.core.DataType; 13 | import org.hamcrest.FeatureMatcher; 14 | import org.hamcrest.Matcher; 15 | import org.junit.Test; 16 | 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | import static org.hamcrest.Matchers.*; 19 | 20 | public class TimezoneSnapshotTest { 21 | 22 | @Test 23 | public void testTimezoneColumns() throws Exception { 24 | Database database = CommandLineUtils.createDatabaseObject(new ClassLoaderResourceAccessor(this.getClass().getClassLoader()), "hibernate:spring:com.example.timezone?dialect=org.hibernate.dialect.H2Dialect", null, null, null, null, null, false, false, null, null, null, null, null, null, null); 25 | 26 | DatabaseSnapshot snapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(CatalogAndSchema.DEFAULT, database, new SnapshotControl(database)); 27 | 28 | assertThat( 29 | snapshot.get(Column.class), 30 | hasItems( 31 | // Instant column should result in 'timestamp with timezone' type 32 | allOf( 33 | hasProperty("name", equalTo("timestamp1")), 34 | hasDatabaseAttribute("type", DataType.class, hasProperty("typeName", equalTo("timestamp with timezone"))) 35 | ), 36 | // LocalDateTime column should result in 'timestamp' type 37 | allOf( 38 | hasProperty("name", equalTo("timestamp2")), 39 | hasDatabaseAttribute("type", DataType.class, hasProperty("typeName", equalTo("timestamp"))) 40 | ), 41 | // Instant column with explicit definition 'timestamp' should result in 'timestamp' type 42 | allOf( 43 | hasProperty("name", equalTo("timestamp3")), 44 | hasDatabaseAttribute("type", DataType.class, hasProperty("typeName", equalTo("timestamp"))) 45 | ), 46 | // LocalDateTime Colum with explicit definition 'TIMESTAMP WITH TIME ZONE' should result in 'TIMESTAMP with timezone' type 47 | allOf( 48 | hasProperty("name", equalTo("timestamp4")), 49 | hasDatabaseAttribute("type", DataType.class, hasProperty("typeName", equalToIgnoringCase("timestamp with timezone"))) 50 | ) 51 | ) 52 | ); 53 | } 54 | 55 | private static FeatureMatcher hasDatabaseAttribute(String attribute, Class type, Matcher matcher) { 56 | return new FeatureMatcher<>(matcher, attribute, attribute) { 57 | 58 | @Override 59 | protected T featureValueOf(DatabaseObject databaseObject) { 60 | return databaseObject.getAttribute(attribute, type); 61 | } 62 | 63 | }; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/liquibase/harness/diff/Authors.java: -------------------------------------------------------------------------------- 1 | package liquibase.harness.diff; 2 | 3 | import java.sql.Timestamp; 4 | import java.sql.Date; 5 | 6 | public class Authors { 7 | int id; 8 | String firstName; 9 | String lastName; 10 | String email; 11 | Date birthdate; 12 | Timestamp added; 13 | 14 | public Authors() { 15 | } 16 | 17 | public Authors(int id, String firstName, String lastName, String email, Date birthdate, Timestamp added) { 18 | this.id = id; 19 | this.firstName = firstName; 20 | this.lastName = lastName; 21 | this.email = email; 22 | this.birthdate = birthdate; 23 | this.added = added; 24 | } 25 | 26 | public int getId() { 27 | return id; 28 | } 29 | 30 | public void setId(int id) { 31 | this.id = id; 32 | } 33 | 34 | public String getFirstName() { 35 | return firstName; 36 | } 37 | 38 | public void setFirstName(String firstName) { 39 | this.firstName = firstName; 40 | } 41 | 42 | public String getLastName() { 43 | return lastName; 44 | } 45 | 46 | public void setLastName(String lastName) { 47 | this.lastName = lastName; 48 | } 49 | 50 | public String getEmail() { 51 | return email; 52 | } 53 | 54 | public void setEmail(String email) { 55 | this.email = email; 56 | } 57 | 58 | public Date getBirthdate() { 59 | return birthdate; 60 | } 61 | 62 | public void setBirthdate(Date birthdate) { 63 | this.birthdate = birthdate; 64 | } 65 | 66 | public Timestamp getAdded() { 67 | return added; 68 | } 69 | 70 | public void setAdded(Timestamp added) { 71 | this.added = added; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/liquibase/harness/diff/Posts.java: -------------------------------------------------------------------------------- 1 | package liquibase.harness.diff; 2 | 3 | import java.sql.Date; 4 | 5 | public class Posts { 6 | int id; 7 | int authorId; //TODO this might not be needed depending on mapping strategy 8 | String title; 9 | String description; 10 | String content; 11 | Date insertedDate; 12 | 13 | public Posts() { 14 | } 15 | 16 | public Posts(int id, int authorId, String title, String description, String content, Date insertedDate) { 17 | this.id = id; 18 | this.authorId = authorId; 19 | this.title = title; 20 | this.description = description; 21 | this.content = content; 22 | this.insertedDate = insertedDate; 23 | } 24 | 25 | public int getId() { 26 | return id; 27 | } 28 | 29 | public void setId(int id) { 30 | this.id = id; 31 | } 32 | 33 | public int getAuthorId() { 34 | return authorId; 35 | } 36 | 37 | public void setAuthorId(int authorId) { 38 | this.authorId = authorId; 39 | } 40 | 41 | public String getTitle() { 42 | return title; 43 | } 44 | 45 | public void setTitle(String title) { 46 | this.title = title; 47 | } 48 | 49 | public String getDescription() { 50 | return description; 51 | } 52 | 53 | public void setDescription(String description) { 54 | this.description = description; 55 | } 56 | 57 | public String getContent() { 58 | return content; 59 | } 60 | 61 | public void setContent(String content) { 62 | this.content = content; 63 | } 64 | 65 | public Date getInsertedDate() { 66 | return insertedDate; 67 | } 68 | 69 | public void setInsertedDate(Date insertedDate) { 70 | this.insertedDate = insertedDate; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | com.example.ejb3.auction.AuctionInfo 8 | com.example.ejb3.auction.AuctionItem 9 | com.example.ejb3.auction.Bid 10 | com.example.ejb3.auction.BuyNow 11 | com.example.ejb3.auction.User 12 | com.example.ejb3.auction.Watcher 13 | com.example.ejb3.auction.Item 14 | com.example.ejb3.auction.AuditedItem 15 | com.example.ejb3.auction.FirstTable 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/resources/com/example/pojo/Hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 1 12 | 13 | java:/data 14 | 15 | 16 | 17 | org.hibernate.dialect.H2Dialect 18 | 19 | 20 | org.hibernate.cache.HashtableCacheProvider 21 | false 22 | 0 23 | false 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/com/example/pojo/auction/AuctionItem.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | 10 | 11 | An item that is being auctioned. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/com/example/pojo/auction/Bid.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 10 | A bid or "buy now" for an item. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Y if a "buy now", N if a regular bid. 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 33 | 34 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/com/example/pojo/auction/User.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | Users may bid for or sell auction items. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 31 | 33 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/test/resources/harness-config.yml: -------------------------------------------------------------------------------- 1 | inputFormat: xml 2 | context: testContext 3 | 4 | databasesUnderTest: 5 | - name: hibernateClassic 6 | url: hibernate:classic:liquibase/harness/diff/xml/Hibernate.cfg.xml 7 | # username: 8 | # password: 9 | 10 | - name: h2 11 | url: jdbc:h2:tcp://localhost:1523/test -------------------------------------------------------------------------------- /src/test/resources/liquibase/harness/diff/diffDatabases.yml: -------------------------------------------------------------------------------- 1 | # note database names should match with ones provided in harness-config.yml 2 | --- 3 | references: 4 | - targetDatabaseName: h2 5 | referenceDatabaseName: hibernateClassic 6 | expectedDiffs: 7 | missingObjects: 8 | unexpectedObjects: 9 | changedObjects: 10 | - diffName: "HIBERNATE.authors.email" 11 | diffs: 12 | type : "type changed from 'varchar(255)' to 'VARCHAR(100 BYTE)'" 13 | order: "order changed from 'null' to '4'" 14 | - diffName: "HIBERNATE.authors.added" 15 | diffs: 16 | defaultValue : "defaultValue changed from 'null' to 'CURRENT_TIMESTAMP()'" 17 | order: "order changed from 'null' to '6'" 18 | - diffName: "HIBERNATE.authors.last_name" 19 | diffs: 20 | type : "type changed from 'varchar(255)' to 'VARCHAR(50 BYTE)'" 21 | order: "order changed from 'null' to '3'" 22 | - diffName: "HIBERNATE.posts.content" 23 | diffs: 24 | type : "type changed from 'varchar(255)' to 'CLOB(2147483647)'" 25 | order: "order changed from 'null' to '5'" 26 | - diffName: "HIBERNATE.authors.birthdate" 27 | diffs: 28 | order: "order changed from 'null' to '5'" 29 | - diffName: "HIBERNATE.authors.first_name" 30 | diffs: 31 | type : "type changed from 'varchar(255)' to 'VARCHAR(50 BYTE)'" 32 | order: "order changed from 'null' to '2'" 33 | - diffName: "HIBERNATE.authors.id" 34 | diffs: 35 | order: "order changed from 'null' to '1'" 36 | - diffName: "HIBERNATE.posts.description" 37 | diffs: 38 | type : "type changed from 'varchar(255)' to 'VARCHAR(500 BYTE)'" 39 | order: "order changed from 'null' to '4'" 40 | - diffName: "HIBERNATE.posts.inserted_date" 41 | diffs: 42 | order: "order changed from 'null' to '6'" 43 | - diffName: "HIBERNATE.posts.title" 44 | diffs: 45 | type : "type changed from 'varchar(255)' to 'VARCHAR(255 BYTE)'" 46 | order: "order changed from 'null' to '3'" 47 | - diffName: "HIBERNATE.posts.author_id" 48 | diffs: 49 | order: "order changed from 'null' to '2'" 50 | - diffName: "HIBERNATE.posts.id" 51 | diffs: 52 | order: "order changed from 'null' to '1'" -------------------------------------------------------------------------------- /src/test/resources/liquibase/harness/diff/xml/Authors.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Author of the post 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/resources/liquibase/harness/diff/xml/Hibernate.cfg.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 1 12 | 13 | java:/data 14 | 15 | 16 | 17 | org.hibernate.dialect.H2Dialect 18 | 19 | 20 | org.hibernate.cache.HashtableCacheProvider 21 | false 22 | 0 23 | false 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/resources/liquibase/harness/diff/xml/Posts.hbm.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | details about posts 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/spring.ctx.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | com.example.pojo.auction.Watcher 12 | 13 | 14 | 15 | 16 | classpath*:com/example/pojo/auction/**/*.hbm.xml 17 | 18 | 19 | 20 | 21 | java:comp/env/jdbc/spark 22 | org.hibernate.dialect.MySQLInnoDBDialect 23 | update 24 | false 25 | false 26 | false 27 | true 28 | true 29 | org.hibernate.cache.EhCacheProvider 30 | /ehcache.xml 31 | 32 | 33 | 34 | --------------------------------------------------------------------------------