├── settings.gradle ├── photon-core ├── infinitest.filters ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── molcikas │ │ │ └── photon │ │ │ ├── options │ │ │ ├── DefaultTableName.java │ │ │ └── PhotonOptions.java │ │ │ ├── converters │ │ │ ├── Converter.java │ │ │ ├── EnumConverterFactory.java │ │ │ ├── ConvertersProvider.java │ │ │ ├── ConverterException.java │ │ │ ├── number │ │ │ │ ├── ByteConverter.java │ │ │ │ ├── LongConverter.java │ │ │ │ ├── ShortConverter.java │ │ │ │ ├── FloatConverter.java │ │ │ │ ├── DoubleConverter.java │ │ │ │ ├── IntegerConverter.java │ │ │ │ ├── BigDecimalConverter.java │ │ │ │ └── NumberConverter.java │ │ │ ├── InputStreamConverter.java │ │ │ ├── UUIDConverter.java │ │ │ ├── date │ │ │ │ ├── InstantConverter.java │ │ │ │ ├── LocalDateConverter.java │ │ │ │ ├── ZonedDateTimeConverter.java │ │ │ │ ├── LocalDateTimeConverter.java │ │ │ │ ├── DateConverter.java │ │ │ │ └── TimestampConverter.java │ │ │ ├── IOUtils.java │ │ │ ├── DefaultEnumConverterFactory.java │ │ │ ├── BooleanConverter.java │ │ │ ├── StringConverter.java │ │ │ └── ByteArrayConverter.java │ │ │ ├── blueprints │ │ │ ├── entity │ │ │ │ ├── FieldType.java │ │ │ │ ├── ChildCollectionConstructor.java │ │ │ │ ├── EntityBlueprintAndKey.java │ │ │ │ ├── FieldBlueprintAndKey.java │ │ │ │ ├── EntityClassDiscriminator.java │ │ │ │ ├── CompoundEntityFieldValueMapping.java │ │ │ │ ├── EntityFieldValueMapping.java │ │ │ │ ├── MappedClassBlueprint.java │ │ │ │ └── FlattenedCollectionBlueprint.java │ │ │ ├── table │ │ │ │ ├── JoinType.java │ │ │ │ ├── TableBlueprintAndKey.java │ │ │ │ ├── DefaultColumnDataTypeResult.java │ │ │ │ ├── DatabaseColumnDefinition.java │ │ │ │ ├── ColumnDataType.java │ │ │ │ ├── TableValue.java │ │ │ │ ├── JoinedTableBlueprintBuilder.java │ │ │ │ └── ColumnBlueprint.java │ │ │ └── AggregateBlueprint.java │ │ │ ├── exceptions │ │ │ ├── PhotonOptimisticConcurrencyException.java │ │ │ └── PhotonException.java │ │ │ ├── sqlbuilders │ │ │ ├── SqlBuilderApplyOptionsService.java │ │ │ └── SqlJoinClauseBuilderService.java │ │ │ ├── PhotonUtils.java │ │ │ ├── query │ │ │ ├── GetParameterValuesResult.java │ │ │ ├── PhotonQueryResultRow.java │ │ │ ├── ParameterValue.java │ │ │ ├── PhotonSqlParameter.java │ │ │ └── PhotonAggregateFilterQuery.java │ │ │ └── datasource │ │ │ ├── ExistingConnectionDataSource.java │ │ │ └── GenericDataSource.java │ └── test │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── molcikas │ │ │ └── photon │ │ │ └── tests │ │ │ ├── unit │ │ │ ├── entities │ │ │ │ ├── someaggregate │ │ │ │ │ ├── SomeClass.java │ │ │ │ │ └── SomeAggregate.java │ │ │ │ ├── fieldtest │ │ │ │ │ ├── TestEnum.java │ │ │ │ │ └── FieldTest.java │ │ │ │ ├── shape │ │ │ │ │ ├── Drawing.java │ │ │ │ │ ├── CornerCoordinates.java │ │ │ │ │ ├── Circle.java │ │ │ │ │ ├── Rectangle.java │ │ │ │ │ ├── ShapeColorHistory.java │ │ │ │ │ └── Shape.java │ │ │ │ ├── twoaggregates │ │ │ │ │ ├── AggregateTwo.java │ │ │ │ │ └── AggregateOne.java │ │ │ │ ├── myonetomanytable │ │ │ │ │ ├── MyOneToManyMapTable.java │ │ │ │ │ ├── MyThirdTable.java │ │ │ │ │ ├── MyOneToManyTable.java │ │ │ │ │ └── MyManyTable.java │ │ │ │ ├── mytable │ │ │ │ │ ├── MyOtherTable.java │ │ │ │ │ └── MyTable.java │ │ │ │ ├── product │ │ │ │ │ └── Product.java │ │ │ │ └── recipe │ │ │ │ │ ├── RecipeInstruction.java │ │ │ │ │ ├── RecipeIngredient.java │ │ │ │ │ └── Recipe.java │ │ │ ├── h2 │ │ │ │ ├── H2TestUtil.java │ │ │ │ ├── someaggregate │ │ │ │ │ ├── SomeAggregateDbSetup.java │ │ │ │ │ └── SomeAggregateTests.java │ │ │ │ ├── shape │ │ │ │ │ ├── ShapeMultiTableDbSetup.java │ │ │ │ │ ├── ShapeDbSetup.java │ │ │ │ │ ├── ShapeChildEntityTests.java │ │ │ │ │ └── ShapeQueryTests.java │ │ │ │ ├── product │ │ │ │ │ └── ProductDbSetup.java │ │ │ │ ├── fieldtest │ │ │ │ │ └── FieldTestDbSetup.java │ │ │ │ ├── recipe │ │ │ │ │ └── RecipeDeleteTests.java │ │ │ │ ├── mytable │ │ │ │ │ └── MyTableDbSetup.java │ │ │ │ ├── twoaggregates │ │ │ │ │ └── TwoAggregatesDbSetup.java │ │ │ │ └── myonetomanytable │ │ │ │ │ └── MyOneToManyTableDbSetup.java │ │ │ └── blueprints │ │ │ │ ├── TableValueTest.java │ │ │ │ └── MyTableBlueprintTests.java │ │ │ └── integration │ │ │ ├── PhotonTestTable.java │ │ │ ├── MySqlIntegrationTest.java │ │ │ └── OracleIntegrationTest.java │ │ └── resources │ │ ├── setup-some-aggregate.sql │ │ ├── logback-test.xml │ │ └── setup-shape-multi-table.sql └── build.gradle ├── findbugs-exclude-filter.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── copyright │ └── profiles_settings.xml ├── vcs.xml ├── compiler.xml ├── misc.xml ├── libraries │ ├── Gradle__junit_junit_4_12.xml │ ├── Gradle__com_h2database_h2_1_4_193.xml │ ├── Gradle__joda_time_joda_time_2_9_7.xml │ ├── Gradle__org_hamcrest_hamcrest_core_1_3.xml │ ├── Gradle__org_mockito_mockito_all_1_10_19.xml │ ├── Gradle__org_apache_commons_commons_lang3_3_4.xml │ ├── Gradle__mysql_mysql_connector_java_5_1_40.xml │ └── Gradle__org_codehaus_groovy_groovy_all_2_3_11.xml ├── modules.xml ├── gradle.xml ├── runConfigurations │ ├── All_Core_Unit_Tests.xml │ └── Integration_Tests.xml └── codeStyles │ └── Project.xml ├── findbugs-include-filter.xml ├── .gitignore ├── .travis.yml ├── photon.iml ├── photon-perf-test ├── src │ └── test │ │ ├── resources │ │ ├── logback-test.xml │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── molcikas │ │ └── photon │ │ └── perf │ │ ├── photon │ │ ├── RecipeInstruction.java │ │ ├── RecipeIngredient.java │ │ └── Recipe.java │ │ ├── hibernate │ │ ├── RecipeInstructionEntity.java │ │ ├── RecipeIngredientEntity.java │ │ └── RecipeEntity.java │ │ ├── ReflectionTest.java │ │ └── RecipeDbSetup.java └── build.gradle ├── docker-compose.yml ├── LICENSE ├── classes └── test │ └── photon-perf-test │ └── META-INF │ └── persistence.xml ├── gradlew.bat └── docs ├── AdvancedLoadingAndSaving.md ├── ValueObjects.md └── ViewModels.md /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'photon-core' 2 | include 'photon-perf-test' -------------------------------------------------------------------------------- /photon-core/infinitest.filters: -------------------------------------------------------------------------------- 1 | com\.github\.molcikas\.photon\.tests\.integration\..* -------------------------------------------------------------------------------- /findbugs-exclude-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ossrhUsername= 2 | ossrhPassword= 3 | developerId= 4 | developerName= 5 | developerEmailAddress= 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molcikas/photon/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /findbugs-include-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/options/DefaultTableName.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.options; 2 | 3 | public enum DefaultTableName 4 | { 5 | ClassName, 6 | ClassNameLowerCase 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/Converter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | /** 4 | * Represents a converter. 5 | */ 6 | public interface Converter { 7 | 8 | T convert(Object val) throws ConverterException; 9 | } 10 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/FieldType.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | public enum FieldType 4 | { 5 | Primitive, 6 | Entity, 7 | EntityList, 8 | CustomValueMapper, 9 | CompoundCustomValueMapper, 10 | FlattenedCollection, 11 | } 12 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/EnumConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into an {@link Enum}. 5 | */ 6 | public interface EnumConverterFactory { 7 | Converter newConverter(Class enumClass); 8 | } 9 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/ConvertersProvider.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * User: dimzon 7 | * Date: 4/24/14 8 | * Time: 12:53 AM 9 | */ 10 | public interface ConvertersProvider { 11 | void fill(Map, Converter> mapToFill); 12 | } 13 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/someaggregate/SomeClass.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.someaggregate; 2 | 3 | public class SomeClass 4 | { 5 | private int id; 6 | 7 | private SomeClass() 8 | { 9 | } 10 | 11 | public SomeClass(int id) 12 | { 13 | this.id = id; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/exceptions/PhotonOptimisticConcurrencyException.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.exceptions; 2 | 3 | public class PhotonOptimisticConcurrencyException extends RuntimeException 4 | { 5 | public PhotonOptimisticConcurrencyException() 6 | { 7 | super("An optimistic concurrency exception occurred."); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/ChildCollectionConstructor.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import java.util.Collection; 4 | 5 | public interface ChildCollectionConstructor 6 | { 7 | Collection toCollection(F fieldValue, P parentEntityInstance); 8 | 9 | F toFieldValue(Collection collection, P parentEntityInstance); 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | /.idea/workspace.xml 15 | /.idea/tasks.xml 16 | /.gradle 17 | !gradle-wrapper.jar 18 | /classes 19 | test/libs/* 20 | 21 | *.iml 22 | !/photon.iml 23 | 24 | /photon-core/build 25 | /photon-perf-test/build -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/fieldtest/TestEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.fieldtest; 2 | 3 | public enum TestEnum 4 | { 5 | VALUE_ZERO, 6 | VALUE_ONE, 7 | VALUE_TWO, 8 | VALUE_THREE; 9 | 10 | @Override 11 | public String toString() 12 | { 13 | return "Hopefully nothing relies on toString() returning the enum value because it's not!"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/Drawing.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | import java.util.List; 4 | 5 | public class Drawing 6 | { 7 | private int id; 8 | 9 | private List shapes; 10 | 11 | public int getId() 12 | { 13 | return id; 14 | } 15 | 16 | public List getShapes() 17 | { 18 | return shapes; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/JoinType.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | public enum JoinType 4 | { 5 | InnerJoin("JOIN"), 6 | LeftOuterJoin("LEFT JOIN"); 7 | 8 | private final String joinSql; 9 | 10 | public String getJoinSql() 11 | { 12 | return joinSql; 13 | } 14 | 15 | JoinType(String joinSql) 16 | { 17 | this.joinSql = joinSql; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/ConverterException.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | /** 4 | * Represents an exception thrown from a converter. 5 | */ 6 | public class ConverterException extends RuntimeException 7 | { 8 | public ConverterException(String message) { 9 | super(message); 10 | } 11 | 12 | public ConverterException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/exceptions/PhotonException.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.exceptions; 2 | 3 | public class PhotonException extends RuntimeException 4 | { 5 | public PhotonException(String message, Object... args) 6 | { 7 | super(String.format(message, args)); 8 | } 9 | 10 | public PhotonException(Throwable cause, String message, Object... args) 11 | { 12 | super(String.format(message, args), cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | services: 4 | - docker 5 | 6 | language: java 7 | jdk: 8 | - oraclejdk8 9 | 10 | before_cache: 11 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 12 | 13 | cache: 14 | directories: 15 | - "$HOME/.gradle/caches/" 16 | - "$HOME/.gradle/wrapper/" 17 | 18 | before_install: 19 | - chmod +x gradlew 20 | 21 | before_script: 22 | - docker-compose up -d 23 | - sleep 30 24 | 25 | after_success: 26 | - bash <(curl -s https://codecov.io/bash) 27 | -------------------------------------------------------------------------------- /photon.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/EntityBlueprintAndKey.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import com.github.molcikas.photon.blueprints.table.TableValue; 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | 8 | @AllArgsConstructor 9 | @Getter 10 | @EqualsAndHashCode 11 | public class EntityBlueprintAndKey 12 | { 13 | private final EntityBlueprint entityBlueprint; 14 | private final TableValue primaryKey; 15 | } 16 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/someaggregate/SomeAggregate.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.someaggregate; 2 | 3 | import java.util.List; 4 | 5 | public class SomeAggregate 6 | { 7 | private int id; 8 | 9 | private List fieldOne; 10 | 11 | private SomeAggregate() 12 | { 13 | } 14 | 15 | public SomeAggregate(int id, List fieldOne) 16 | { 17 | this.id = id; 18 | this.fieldOne = fieldOne; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__junit_junit_4_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/sqlbuilders/SqlBuilderApplyOptionsService.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.sqlbuilders; 2 | 3 | import com.github.molcikas.photon.options.PhotonOptions; 4 | 5 | public final class SqlBuilderApplyOptionsService 6 | { 7 | public static String applyPhotonOptionsToSql(String sql, PhotonOptions photonOptions) 8 | { 9 | return sql 10 | .replaceAll("\\[", photonOptions.getDelimitIdentifierStart()) 11 | .replaceAll("\\]", photonOptions.getDelimitIdentifierEnd()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/twoaggregates/AggregateTwo.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.twoaggregates; 2 | 3 | import java.util.UUID; 4 | 5 | public class AggregateTwo 6 | { 7 | private UUID aggregateTwoId; 8 | 9 | private String myValue; 10 | 11 | public UUID getAggregateTwoId() 12 | { 13 | return aggregateTwoId; 14 | } 15 | 16 | public String getMyValue() 17 | { 18 | return myValue; 19 | } 20 | 21 | private AggregateTwo() 22 | { 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__com_h2database_h2_1_4_193.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__joda_time_joda_time_2_9_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/TableBlueprintAndKey.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | @EqualsAndHashCode 10 | public class TableBlueprintAndKey 11 | { 12 | private final TableBlueprint tableBlueprint; 13 | private final TableValue primaryKey; 14 | 15 | @Override 16 | public String toString() 17 | { 18 | return tableBlueprint.getTableName() + ":" + primaryKey; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /photon-core/src/test/resources/setup-some-aggregate.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `someaggregate`; 2 | DROP TABLE IF EXISTS `someclass`; 3 | 4 | CREATE TABLE `someaggregate` ( 5 | `id` int(11) NOT NULL AUTO_INCREMENT, 6 | PRIMARY KEY (`id`) 7 | ); 8 | 9 | CREATE TABLE `someclass` ( 10 | `id` int(11) NOT NULL AUTO_INCREMENT, 11 | `someAggregateId` int(11) NOT NULL, 12 | PRIMARY KEY (`id`), 13 | CONSTRAINT `someclass_someaggregate` FOREIGN KEY (`someAggregateId`) REFERENCES `someaggregate` (`id`) 14 | ); 15 | 16 | insert into `someaggregate` (`id`) values (1); 17 | insert into `someclass` (`id`, `someAggregateId`) values (1, 1); -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_mockito_mockito_all_1_10_19.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /photon-core/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_apache_commons_commons_lang3_3_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__mysql_mysql_connector_java_5_1_40.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/Gradle__org_codehaus_groovy_groovy_all_2_3_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/FieldBlueprintAndKey.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import com.github.molcikas.photon.blueprints.table.TableValue; 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | 8 | @AllArgsConstructor 9 | @Getter 10 | @EqualsAndHashCode 11 | public class FieldBlueprintAndKey 12 | { 13 | private final FieldBlueprint fieldBlueprint; 14 | private final TableValue primaryKey; 15 | 16 | @Override 17 | public String toString() 18 | { 19 | return fieldBlueprint.getFieldClass().getSimpleName() + "." + fieldBlueprint.getFieldName() + ":" + primaryKey; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/myonetomanytable/MyOneToManyMapTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.myonetomanytable; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.util.Map; 7 | 8 | @AllArgsConstructor 9 | public class MyOneToManyMapTable 10 | { 11 | @Getter 12 | private Integer myOneToManyMapTableId; 13 | 14 | @Getter 15 | private String myvalue; 16 | 17 | @Getter 18 | private Map myManyTables; 19 | 20 | private MyOneToManyMapTable() 21 | { 22 | } 23 | 24 | public void setMyvalue(String myvalue) 25 | { 26 | this.myvalue = myvalue; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/mytable/MyOtherTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.mytable; 2 | 3 | public class MyOtherTable 4 | { 5 | private int id; 6 | 7 | private String myOtherValueWithDiffName; 8 | 9 | public int getId() 10 | { 11 | return id; 12 | } 13 | 14 | public String getMyOtherValueWithDiffName() 15 | { 16 | return myOtherValueWithDiffName; 17 | } 18 | 19 | private MyOtherTable() 20 | { 21 | } 22 | 23 | public MyOtherTable(int id, String myOtherValueWithDiffName) 24 | { 25 | this.id = id; 26 | this.myOtherValueWithDiffName = myOtherValueWithDiffName; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/DefaultColumnDataTypeResult.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | public class DefaultColumnDataTypeResult 4 | { 5 | public final boolean foundDataType; 6 | public final ColumnDataType dataType; 7 | 8 | public DefaultColumnDataTypeResult(ColumnDataType dataType) 9 | { 10 | this.foundDataType = true; 11 | this.dataType = dataType; 12 | } 13 | 14 | public DefaultColumnDataTypeResult() 15 | { 16 | this.foundDataType = false; 17 | this.dataType = null; 18 | } 19 | 20 | public static DefaultColumnDataTypeResult notFound() 21 | { 22 | return new DefaultColumnDataTypeResult(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/PhotonUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon; 2 | 3 | import java.util.Set; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.Stream; 6 | 7 | public class PhotonUtils 8 | { 9 | private static final Set> primitiveNumbers = Stream 10 | .of(int.class, long.class, float.class, double.class, byte.class, short.class) 11 | .collect(Collectors.toSet()); 12 | 13 | public static boolean isNumericType(Class cls) 14 | { 15 | if (cls.isPrimitive()) 16 | { 17 | return primitiveNumbers.contains(cls); 18 | } 19 | else 20 | { 21 | return Number.class.isAssignableFrom(cls); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/myonetomanytable/MyThirdTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.myonetomanytable; 2 | 3 | public class MyThirdTable 4 | { 5 | private Integer id; 6 | 7 | private Integer parent; 8 | 9 | private String val; 10 | 11 | public Integer getId() 12 | { 13 | return id; 14 | } 15 | 16 | public Integer getParent() 17 | { 18 | return parent; 19 | } 20 | 21 | public String getVal() 22 | { 23 | return val; 24 | } 25 | 26 | private MyThirdTable() 27 | { 28 | } 29 | 30 | public MyThirdTable(Integer parent, String val) 31 | { 32 | this.parent = parent; 33 | this.val = val; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/ByteConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into a {@link Byte}. 5 | */ 6 | public class ByteConverter extends NumberConverter { 7 | 8 | public ByteConverter(boolean primitive) { 9 | super(primitive); 10 | } 11 | 12 | @Override 13 | protected Byte convertNumberValue(Number val) { 14 | return val.byteValue(); 15 | } 16 | 17 | @Override 18 | protected Byte convertStringValue(String val) { 19 | return Byte.parseByte(val); 20 | } 21 | 22 | @Override 23 | protected String getTypeDescription() { 24 | return Byte.class.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/LongConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into a {@link Long}. 5 | */ 6 | public class LongConverter extends NumberConverter { 7 | 8 | public LongConverter(boolean primitive) { 9 | super(primitive); 10 | } 11 | 12 | @Override 13 | protected Long convertNumberValue(Number val) { 14 | return val.longValue(); 15 | } 16 | 17 | @Override 18 | protected Long convertStringValue(String val) { 19 | return Long.parseLong(val); 20 | } 21 | 22 | @Override 23 | protected String getTypeDescription() { 24 | return Long.class.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/ShortConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into a {@link Short}. 5 | */ 6 | public class ShortConverter extends NumberConverter { 7 | 8 | public ShortConverter(boolean primitive) { 9 | super(primitive); 10 | } 11 | 12 | @Override 13 | protected Short convertNumberValue(Number val) { 14 | return val.shortValue(); 15 | } 16 | 17 | @Override 18 | protected Short convertStringValue(String val) { 19 | return Short.parseShort(val); 20 | } 21 | 22 | @Override 23 | protected String getTypeDescription() { 24 | return Short.class.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/FloatConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into a {@link Float}. 5 | */ 6 | public class FloatConverter extends NumberConverter 7 | { 8 | 9 | public FloatConverter(boolean primitive) { 10 | super(primitive); 11 | } 12 | 13 | @Override 14 | protected Float convertNumberValue(Number val) { 15 | return val.floatValue(); 16 | } 17 | 18 | @Override 19 | protected Float convertStringValue(String val) { 20 | return Float.parseFloat(val); 21 | } 22 | 23 | @Override 24 | protected String getTypeDescription() { 25 | return Float.class.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/EntityClassDiscriminator.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * The interface for custom entity class discriminators. 7 | */ 8 | public interface EntityClassDiscriminator 9 | { 10 | /** 11 | * Called after entity column values are retrieved but just before the entity is constructed. This allows 12 | * dynamically determining the entity class based on the column values. Typically, this is used to implement 13 | * single-table inheritance. 14 | * 15 | * @param valueMap - the column values for the entity instance 16 | * @return - the entity class to construct 17 | */ 18 | Class getClassForEntity(Map valueMap); 19 | } 20 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/query/GetParameterValuesResult.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.query; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.util.Collections; 7 | import java.util.Map; 8 | 9 | @AllArgsConstructor 10 | @Getter 11 | public class GetParameterValuesResult 12 | { 13 | private boolean skipped; 14 | private boolean changed; 15 | private Map values; 16 | 17 | public static GetParameterValuesResult skipped() 18 | { 19 | return new GetParameterValuesResult(true, false, Collections.emptyMap()); 20 | } 21 | 22 | public static GetParameterValuesResult unchanged() 23 | { 24 | return new GetParameterValuesResult(false, false, Collections.emptyMap()); 25 | } 26 | } -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/DoubleConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into a {@link Double}. 5 | */ 6 | public class DoubleConverter extends NumberConverter 7 | { 8 | 9 | public DoubleConverter(boolean primitive) { 10 | super(primitive); 11 | } 12 | 13 | @Override 14 | protected Double convertNumberValue(Number val) { 15 | return val.doubleValue(); 16 | } 17 | 18 | @Override 19 | protected Double convertStringValue(String val) { 20 | return Double.parseDouble(val); 21 | } 22 | 23 | @Override 24 | protected String getTypeDescription() { 25 | return Double.class.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/IntegerConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | /** 4 | * Used by sql2o to convert a value from the database into an {@link Integer}. 5 | */ 6 | public class IntegerConverter extends NumberConverter 7 | { 8 | 9 | public IntegerConverter(boolean primitive) { 10 | super(primitive); 11 | } 12 | 13 | @Override 14 | protected Integer convertNumberValue(Number val) { 15 | return val.intValue(); 16 | } 17 | 18 | @Override 19 | protected Integer convertStringValue(String val) { 20 | return Integer.parseInt(val); 21 | } 22 | 23 | @Override 24 | protected String getTypeDescription() { 25 | return Integer.class.toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/InputStreamConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.io.ByteArrayInputStream; 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: lars 8 | * Date: 6/13/13 9 | * Time: 11:40 PM 10 | * To change this template use File | Settings | File Templates. 11 | */ 12 | public class InputStreamConverter implements Converter 13 | { 14 | public ByteArrayInputStream convert(Object val) throws ConverterException { 15 | if (val == null) return null; 16 | 17 | try { 18 | return new ByteArrayInputStream( new ByteArrayConverter().convert(val) ); 19 | } catch( ConverterException e) { 20 | throw new ConverterException("Error converting Blob to InputSteam"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/product/Product.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.product; 2 | 3 | import org.apache.commons.lang3.math.Fraction; 4 | 5 | public class Product 6 | { 7 | private int theProductId; 8 | 9 | private Fraction quantity; 10 | 11 | public int getTheProductId() 12 | { 13 | return theProductId; 14 | } 15 | 16 | public Fraction getQuantity() 17 | { 18 | return quantity; 19 | } 20 | 21 | public void setQuantity(Fraction quantity) 22 | { 23 | this.quantity = quantity; 24 | } 25 | 26 | private Product() 27 | { 28 | } 29 | 30 | public Product(int theProductId, Fraction quantity) 31 | { 32 | this.theProductId = theProductId; 33 | this.quantity = quantity; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/CornerCoordinates.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | public class CornerCoordinates 4 | { 5 | private Integer id; 6 | 7 | private Integer x; 8 | 9 | private Integer y; 10 | 11 | public Integer getId() 12 | { 13 | return id; 14 | } 15 | 16 | public Integer getX() 17 | { 18 | return x; 19 | } 20 | 21 | public Integer getY() 22 | { 23 | return y; 24 | } 25 | 26 | private CornerCoordinates() 27 | { 28 | } 29 | 30 | public CornerCoordinates(Integer id, Integer x, Integer y) 31 | { 32 | this.id = id; 33 | this.x = x; 34 | this.y = y; 35 | } 36 | 37 | public void setX(Integer x) 38 | { 39 | this.x = x; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.3' 2 | 3 | services: 4 | 5 | mysql57: 6 | image: mysql:5.7.21 7 | restart: always 8 | environment: 9 | MYSQL_ROOT_PASSWORD: bears 10 | TZ: "America/Chicago" 11 | ports: 12 | - 13306:3306 13 | 14 | postgres104: 15 | image: postgres:10.4 16 | environment: 17 | POSTGRES_PASSWORD: bears 18 | ports: 19 | - 15432:5432 20 | 21 | mssql2017: 22 | image: microsoft/mssql-server-linux:2017-latest 23 | environment: 24 | SA_PASSWORD: Gobears123 25 | ACCEPT_EULA: Y 26 | ports: 27 | - 11433:1433 28 | 29 | # This container takes a VERY long time to start (10+ minutes). Leave commented out so it doesn't run during CI build. 30 | # oracle12c: 31 | # image: sath89/oracle-12c 32 | # environment: 33 | # DBCA_TOTAL_MEMORY: 1024 34 | # WEB_CONSOLE: "false" 35 | # ports: 36 | # - 11521:1521 37 | # - 8080:8080 -------------------------------------------------------------------------------- /photon-perf-test/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | description = "Photon performance testing" 3 | 4 | dependencies { 5 | compile project(':photon-core') 6 | 7 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4' 8 | 9 | // Database 10 | testCompile group: 'com.h2database', name: 'h2', version: '1.4.193' 11 | testCompile group: 'mysql', name: 'mysql-connector-java', version: '5.1.40' 12 | testCompile group: 'com.zaxxer', name: 'HikariCP', version: '2.4.7' 13 | testCompile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.3.Final' 14 | testCompile group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.2.3.Final' 15 | testCompile group: 'org.hibernate', name: 'hibernate-hikaricp', version: '5.2.3.Final' 16 | 17 | // Unit testing 18 | testCompile group: 'junit', name: 'junit', version: '4.12' 19 | testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' 20 | } 21 | 22 | test.enabled = false -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/Circle.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | import java.util.List; 4 | 5 | public class Circle extends Shape 6 | { 7 | private int radius; 8 | 9 | public int getRadius() 10 | { 11 | return radius; 12 | } 13 | 14 | private Circle() 15 | { 16 | } 17 | 18 | public Circle(Integer id, String color, Integer drawingId, int radius) 19 | { 20 | super(id, "circle", color, drawingId); 21 | this.radius = radius; 22 | } 23 | 24 | public Circle( 25 | Integer id, 26 | String color, 27 | Integer drawingId, 28 | int radius, 29 | List colorHistory) 30 | { 31 | super(id, "circle", color, drawingId, colorHistory); 32 | this.radius = radius; 33 | } 34 | 35 | public void setRadius(int radius) 36 | { 37 | this.radius = radius; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/twoaggregates/AggregateOne.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.twoaggregates; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public class AggregateOne 7 | { 8 | private UUID aggregateOneId; 9 | 10 | private String myValue; 11 | 12 | private List aggregateTwos; 13 | 14 | public UUID getAggregateOneId() 15 | { 16 | return aggregateOneId; 17 | } 18 | 19 | public String getMyValue() 20 | { 21 | return myValue; 22 | } 23 | 24 | public List getAggregateTwos() 25 | { 26 | return aggregateTwos; 27 | } 28 | 29 | private AggregateOne() 30 | { 31 | } 32 | 33 | public AggregateOne(UUID aggregateOneId, String myValue, List aggregateTwos) 34 | { 35 | this.aggregateOneId = aggregateOneId; 36 | this.myValue = myValue; 37 | this.aggregateTwos = aggregateTwos; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/UUIDConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.UUID; 5 | 6 | /** 7 | * Used by sql2o to convert a value from the database into a {@link UUID}. 8 | */ 9 | public class UUIDConverter implements Converter 10 | { 11 | public UUID convert(Object val) throws ConverterException { 12 | if (val == null){ 13 | return null; 14 | } 15 | 16 | if (val instanceof UUID){ 17 | return (UUID)val; 18 | } 19 | 20 | if(val instanceof String){ 21 | return UUID.fromString((String) val); 22 | } 23 | 24 | if(val instanceof byte[]) { 25 | ByteBuffer buffer = ByteBuffer.wrap((byte[]) val); 26 | return new UUID(buffer.getLong(), buffer.getLong()); 27 | } 28 | 29 | throw new ConverterException("Cannot convert type " + val.getClass() + " " + UUID.class); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/BigDecimalConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /** 6 | * Used by sql2o to convert a value from the database into a {@link BigDecimal}. 7 | */ 8 | public class BigDecimalConverter extends NumberConverter{ 9 | 10 | public BigDecimalConverter() { 11 | super(false); 12 | } 13 | 14 | @Override 15 | protected BigDecimal convertNumberValue(Number val) { 16 | if (val instanceof BigDecimal){ 17 | return (BigDecimal)val; 18 | } 19 | else{ 20 | return BigDecimal.valueOf(val.doubleValue()); 21 | } 22 | } 23 | 24 | @Override 25 | protected BigDecimal convertStringValue(String val) { 26 | return BigDecimal.valueOf(Double.parseDouble(val)); 27 | } 28 | 29 | @Override 30 | protected String getTypeDescription() { 31 | return BigDecimal.class.toString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/H2TestUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | public class H2TestUtil 9 | { 10 | public static final String h2Url = "jdbc:h2:mem:test;MODE=MySql;DB_CLOSE_DELAY=-1"; 11 | public static final String h2User = "sa"; 12 | public static final String h2Password = ""; 13 | 14 | public static File getResourceFile(String name) 15 | { 16 | String filePath = H2TestUtil.class.getResource("/" + name).getFile(); 17 | return new File(filePath); 18 | } 19 | 20 | public static String readResourceFile(String name) 21 | { 22 | File resourceFile = getResourceFile(name); 23 | try 24 | { 25 | return FileUtils.readFileToString(resourceFile, "UTF-8"); 26 | } 27 | catch(IOException ex) 28 | { 29 | throw new RuntimeException(ex); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/myonetomanytable/MyOneToManyTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.myonetomanytable; 2 | 3 | import java.util.List; 4 | 5 | public class MyOneToManyTable 6 | { 7 | private Integer id; 8 | private String myvalue; 9 | private List myManyTables; 10 | 11 | public Integer getId() 12 | { 13 | return id; 14 | } 15 | 16 | public String getMyvalue() 17 | { 18 | return myvalue; 19 | } 20 | 21 | public List getMyManyTables() 22 | { 23 | return myManyTables; 24 | } 25 | 26 | private MyOneToManyTable() 27 | { 28 | } 29 | 30 | public MyOneToManyTable(Integer id, String myvalue, List myManyTables) 31 | { 32 | this.id = id; 33 | this.myvalue = myvalue; 34 | this.myManyTables = myManyTables; 35 | } 36 | 37 | public void setMyvalue(String myvalue) 38 | { 39 | this.myvalue = myvalue; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/Rectangle.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | import lombok.EqualsAndHashCode; 4 | 5 | import java.util.List; 6 | 7 | @EqualsAndHashCode(callSuper = true) 8 | public class Rectangle extends Shape 9 | { 10 | private int width; 11 | 12 | private int height; 13 | 14 | private List corners; 15 | 16 | public int getWidth() 17 | { 18 | return width; 19 | } 20 | 21 | public int getHeight() 22 | { 23 | return height; 24 | } 25 | 26 | public List getCorners() 27 | { 28 | return corners; 29 | } 30 | 31 | private Rectangle() 32 | { 33 | } 34 | 35 | public Rectangle(Integer id, String color, Integer drawingId, int width, int height, List corners) 36 | { 37 | super(id, "rectangle", color, drawingId); 38 | this.width = width; 39 | this.height = height; 40 | this.corners = corners; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/photon/RecipeInstruction.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.photon; 2 | 3 | import java.util.UUID; 4 | 5 | public class RecipeInstruction 6 | { 7 | private UUID recipeInstructionId; 8 | 9 | private int stepNumber; 10 | 11 | private String description; 12 | 13 | public UUID getRecipeInstructionId() 14 | { 15 | return recipeInstructionId; 16 | } 17 | 18 | public int getStepNumber() 19 | { 20 | return stepNumber; 21 | } 22 | 23 | public String getDescription() 24 | { 25 | return description; 26 | } 27 | 28 | public void setDescription(String description) 29 | { 30 | this.description = description; 31 | } 32 | 33 | private RecipeInstruction() 34 | { 35 | } 36 | 37 | public RecipeInstruction(UUID recipeInstructionId, int stepNumber, String description) 38 | { 39 | this.recipeInstructionId = recipeInstructionId; 40 | this.stepNumber = stepNumber; 41 | this.description = description; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/someaggregate/SomeAggregateDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.someaggregate; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class SomeAggregateDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | return setupDatabase(photon); 13 | } 14 | 15 | public static Photon setupDatabase(Photon photon) 16 | { 17 | try(PhotonTransaction transaction = photon.beginTransaction()) 18 | { 19 | String sql = H2TestUtil.readResourceFile("setup-some-aggregate.sql"); 20 | 21 | for(String statement : sql.split(";")) 22 | { 23 | transaction.query(statement.trim()).executeUpdate(); 24 | } 25 | 26 | transaction.commit(); 27 | } 28 | 29 | return photon; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/DatabaseColumnDefinition.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | import com.github.molcikas.photon.exceptions.PhotonException; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | public class DatabaseColumnDefinition 7 | { 8 | private final String columnName; 9 | 10 | private final ColumnDataType columnDataType; 11 | 12 | public String getColumnName() 13 | { 14 | return columnName; 15 | } 16 | 17 | public ColumnDataType getColumnDataType() 18 | { 19 | return columnDataType; 20 | } 21 | 22 | public DatabaseColumnDefinition(String columnName, ColumnDataType columnDataType) 23 | { 24 | if(StringUtils.isBlank(columnName)) 25 | { 26 | throw new PhotonException("Column name cannot be blank."); 27 | } 28 | 29 | this.columnName = columnName; 30 | this.columnDataType = columnDataType; 31 | } 32 | 33 | public DatabaseColumnDefinition(String columnName) 34 | { 35 | this(columnName, null); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 molcikas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/shape/ShapeMultiTableDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.shape; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | import java.util.List; 8 | 9 | public class ShapeMultiTableDbSetup 10 | { 11 | public static Photon setupDatabase() 12 | { 13 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 14 | return setupDatabase(photon); 15 | } 16 | 17 | public static Photon setupDatabase(Photon photon) 18 | { 19 | try(PhotonTransaction transaction = photon.beginTransaction()) 20 | { 21 | String sql = H2TestUtil.readResourceFile("setup-shape-multi-table.sql"); 22 | 23 | for(String statement : sql.split(";")) 24 | { 25 | transaction.query(statement.trim()).executeUpdate(); 26 | } 27 | 28 | transaction.commit(); 29 | } 30 | 31 | return photon; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations/All_Core_Unit_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/InstantConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.ConverterException; 4 | import com.github.molcikas.photon.converters.Converter; 5 | 6 | import java.time.Instant; 7 | import java.util.Date; 8 | 9 | public class InstantConverter implements Converter 10 | { 11 | public Instant convert(Object val) throws ConverterException 12 | { 13 | if (val == null) 14 | { 15 | return null; 16 | } 17 | 18 | if(Instant.class.isAssignableFrom(val.getClass())) 19 | { 20 | return (Instant) val; 21 | } 22 | 23 | if(Date.class.isAssignableFrom(val.getClass())) 24 | { 25 | return Instant.ofEpochMilli(((Date) val).getTime()); 26 | } 27 | 28 | if (val instanceof Number) 29 | { 30 | return Instant.ofEpochMilli(((Number) val).longValue()); 31 | } 32 | 33 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.util.Instant"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/IOUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.Reader; 7 | 8 | public class IOUtils 9 | { 10 | private static final int EOF = -1; 11 | private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; 12 | 13 | public static byte[] toByteArray(InputStream input) throws IOException { 14 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 15 | byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 16 | int n; 17 | while (EOF != (n = input.read(buffer))) { 18 | output.write(buffer, 0, n); 19 | } 20 | return output.toByteArray(); 21 | } 22 | 23 | public static String toString(Reader input) throws IOException { 24 | StringBuilder output = new StringBuilder(); 25 | char[] buffer = new char[DEFAULT_BUFFER_SIZE]; 26 | int n; 27 | while (EOF != (n = input.read(buffer))) { 28 | output.append(buffer, 0, n); 29 | } 30 | return output.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/myonetomanytable/MyManyTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.myonetomanytable; 2 | 3 | import java.util.List; 4 | 5 | public class MyManyTable 6 | { 7 | private Integer id; 8 | 9 | private Integer parent; 10 | 11 | private String myOtherValueWithDiffName; 12 | 13 | private List myThirdTables; 14 | 15 | public Integer getId() 16 | { 17 | return id; 18 | } 19 | 20 | public Integer getParent() 21 | { 22 | return parent; 23 | } 24 | 25 | public String getMyOtherValueWithDiffName() 26 | { 27 | return myOtherValueWithDiffName; 28 | } 29 | 30 | public List getMyThirdTables() 31 | { 32 | return myThirdTables; 33 | } 34 | 35 | private MyManyTable() 36 | { 37 | } 38 | 39 | public MyManyTable(Integer parent, String myOtherValueWithDiffName, List myThirdTables) 40 | { 41 | this.parent = parent; 42 | this.myOtherValueWithDiffName = myOtherValueWithDiffName; 43 | this.myThirdTables = myThirdTables; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/query/PhotonQueryResultRow.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.query; 2 | 3 | import java.util.Collections; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class PhotonQueryResultRow 9 | { 10 | private final Map values; 11 | private Object firstValue; 12 | 13 | public void addValue(String columnName, Object value) 14 | { 15 | if(values.isEmpty()) 16 | { 17 | firstValue = value; 18 | } 19 | values.put(columnName, value); 20 | } 21 | 22 | public Object getValue(String columnName) 23 | { 24 | return values.get(columnName); 25 | } 26 | 27 | public Set> getValues() 28 | { 29 | return values.entrySet(); 30 | } 31 | 32 | public Map getValuesMap() 33 | { 34 | return Collections.unmodifiableMap(values); 35 | } 36 | 37 | public Object getFirstValue() 38 | { 39 | return firstValue; 40 | } 41 | 42 | public PhotonQueryResultRow() 43 | { 44 | values = new HashMap<>(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/blueprints/TableValueTest.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.blueprints; 2 | 3 | import com.github.molcikas.photon.blueprints.table.TableBlueprintAndKey; 4 | import com.github.molcikas.photon.blueprints.table.TableValue; 5 | import com.github.molcikas.photon.converters.Convert; 6 | import org.junit.Test; 7 | 8 | import java.util.UUID; 9 | 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class TableValueTest 13 | { 14 | @Test 15 | public void equals_byteArray_areEqual() 16 | { 17 | UUID uuid = UUID.fromString("3e038307-a9b6-11e6-ab83-0a0027000010"); 18 | byte[] bytes = (byte[]) Convert.getConverter(byte[].class).convert(uuid); 19 | 20 | TableValue value1 = new TableValue(bytes); 21 | TableValue value2 = new TableValue(bytes); 22 | assertTrue(value1.equals(value2)); 23 | 24 | TableBlueprintAndKey tableBlueprintAndKey1 = new TableBlueprintAndKey(null, value1); 25 | TableBlueprintAndKey tableBlueprintAndKey2 = new TableBlueprintAndKey(null, value2); 26 | assertTrue(tableBlueprintAndKey1.equals(tableBlueprintAndKey2)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/mytable/MyTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.mytable; 2 | 3 | public class MyTable 4 | { 5 | private Integer id; 6 | private String myvalue; 7 | private int version; 8 | 9 | private MyOtherTable myOtherTable; 10 | 11 | public Integer getId() 12 | { 13 | return id; 14 | } 15 | 16 | public String getMyvalue() 17 | { 18 | return myvalue; 19 | } 20 | 21 | public int getVersion() 22 | { 23 | return version; 24 | } 25 | 26 | public MyOtherTable getMyOtherTable() 27 | { 28 | return myOtherTable; 29 | } 30 | 31 | private MyTable() 32 | { 33 | } 34 | 35 | public MyTable(Integer id, String myvalue, MyOtherTable myOtherTable) 36 | { 37 | this.id = id; 38 | this.myvalue = myvalue; 39 | this.version = 1; 40 | this.myOtherTable = myOtherTable; 41 | } 42 | 43 | public void setMyvalue(String myvalue) 44 | { 45 | this.myvalue = myvalue; 46 | } 47 | 48 | public void setVersion(int version) 49 | { 50 | this.version = version; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Integration_Tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/ShapeColorHistory.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | import java.time.ZonedDateTime; 4 | 5 | public class ShapeColorHistory 6 | { 7 | private int id; 8 | 9 | private int shapeId; 10 | 11 | private ZonedDateTime dateChanged; 12 | 13 | private String colorName; 14 | 15 | public int getId() 16 | { 17 | return id; 18 | } 19 | 20 | public int getShapeId() 21 | { 22 | return shapeId; 23 | } 24 | 25 | public ZonedDateTime getDateChanged() 26 | { 27 | return dateChanged; 28 | } 29 | 30 | public String getColorName() 31 | { 32 | return colorName; 33 | } 34 | 35 | private ShapeColorHistory() 36 | { 37 | } 38 | 39 | public ShapeColorHistory(int id, int shapeId, ZonedDateTime dateChanged, String colorName) 40 | { 41 | this.id = id; 42 | this.shapeId = shapeId; 43 | this.dateChanged = dateChanged; 44 | this.colorName = colorName; 45 | } 46 | 47 | public void setColorName(String colorName) 48 | { 49 | this.colorName = colorName; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/hibernate/RecipeInstructionEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.hibernate; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | import javax.persistence.Table; 7 | import java.util.UUID; 8 | 9 | @Entity 10 | @Table(name = "recipeinstruction") 11 | public class RecipeInstructionEntity 12 | { 13 | @Id 14 | @Column(columnDefinition = "BINARY(16)", length = 16) 15 | public UUID recipeInstructionId; 16 | 17 | @Column(columnDefinition = "BINARY(16)", length = 16) 18 | public UUID recipeId; 19 | 20 | @Column 21 | public int stepNumber; 22 | 23 | @Column 24 | public String description; 25 | 26 | public void setDescription(String description) 27 | { 28 | this.description = description; 29 | } 30 | 31 | protected RecipeInstructionEntity() 32 | { 33 | } 34 | 35 | public RecipeInstructionEntity(UUID recipeInstructionId, UUID recipeId, int stepNumber, String description) 36 | { 37 | this.recipeInstructionId = recipeInstructionId; 38 | this.recipeId = recipeId; 39 | this.stepNumber = stepNumber; 40 | this.description = description; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/ColumnDataType.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | public enum ColumnDataType 4 | { 5 | BIT(-7), 6 | TINYINT(-6), 7 | SMALLINT(5), 8 | INTEGER(4), 9 | BIGINT(-5), 10 | FLOAT(6), 11 | REAL(7), 12 | DOUBLE(8), 13 | NUMERIC(2), 14 | DECIMAL(3), 15 | CHAR(1), 16 | VARCHAR(12), 17 | LONGVARCHAR(-1), 18 | DATE(91), 19 | TIME(92), 20 | TIMESTAMP(93), 21 | BINARY(-2), 22 | VARBINARY(-3), 23 | LONGVARBINARY(-4), 24 | NULL(0), 25 | OTHER(1111), 26 | JAVA_OBJECT(2000), 27 | DISTINCT(2001), 28 | STRUCT(2002), 29 | ARRAY(2003), 30 | BLOB(2004), 31 | CLOB(2005), 32 | REF(2006), 33 | DATALINK(70), 34 | BOOLEAN(16), 35 | ROWID(-8), 36 | NCHAR(-15), 37 | NVARCHAR(-9), 38 | LONGNVARCHAR(-16), 39 | NCLOB(2011), 40 | SQLXML(2009), 41 | REF_CURSOR(2012), 42 | TIME_WITH_TIMEZONE(2013), 43 | TIMESTAMP_WITH_TIMEZONE(2014); 44 | 45 | private final int jdbcType; 46 | 47 | public int getJdbcType() 48 | { 49 | return jdbcType; 50 | } 51 | 52 | ColumnDataType(int jdbcType) 53 | { 54 | this.jdbcType = jdbcType; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/LocalDateConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | import com.github.molcikas.photon.converters.ConverterException; 5 | 6 | import java.time.LocalDate; 7 | import java.util.Date; 8 | 9 | public class LocalDateConverter implements Converter 10 | { 11 | private static final long MILLSECONDS_PER_DAY = 86400000; 12 | 13 | public LocalDate convert(Object val) throws ConverterException 14 | { 15 | if (val == null) 16 | { 17 | return null; 18 | } 19 | 20 | if(LocalDate.class.isAssignableFrom(val.getClass())) 21 | { 22 | return (LocalDate) val; 23 | } 24 | 25 | if(Date.class.isAssignableFrom(val.getClass())) 26 | { 27 | return LocalDate.ofEpochDay(((Date) val).getTime() / MILLSECONDS_PER_DAY); 28 | } 29 | 30 | if (val instanceof Number) 31 | { 32 | return LocalDate.ofEpochDay(((Number) val).longValue() / MILLSECONDS_PER_DAY); 33 | } 34 | 35 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.util.LocalDate"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/CompoundEntityFieldValueMapping.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Interface for custom mapping a group of database values to and from one or more entity fields. 7 | * 8 | * @param - The entity class type 9 | */ 10 | public interface CompoundEntityFieldValueMapping 11 | { 12 | /** 13 | * Get the database values from a given entity instance. 14 | * 15 | * @param entityInstance - The entity instance 16 | * @return - The database values. The key is the column name and the value is the database value. 17 | */ 18 | Map getDatabaseValues(E entityInstance); 19 | 20 | /** 21 | * Set a given set of database values on a given entity instance. 22 | * 23 | * @param entityInstance - The entity instance 24 | * @param databaseValues - The database values. The key is the column name and the value is the database value. 25 | * @return - The field values to set on the entity. The key is the field name and the value is the field value. If 26 | * the values were applied directly to the entity instance, then null or an empty map can be returned. 27 | */ 28 | Map setFieldValues(E entityInstance, Map databaseValues); 29 | } 30 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/ZonedDateTimeConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | import com.github.molcikas.photon.converters.ConverterException; 5 | 6 | import java.time.*; 7 | import java.util.Date; 8 | 9 | public class ZonedDateTimeConverter implements Converter 10 | { 11 | public ZonedDateTime convert(Object val) throws ConverterException 12 | { 13 | if (val == null) 14 | { 15 | return null; 16 | } 17 | 18 | if(ZonedDateTime.class.isAssignableFrom(val.getClass())) 19 | { 20 | return (ZonedDateTime) val; 21 | } 22 | 23 | if(Date.class.isAssignableFrom(val.getClass())) 24 | { 25 | Instant instant = Instant.ofEpochMilli(((Date) val).getTime()); 26 | return ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); 27 | } 28 | 29 | if (val instanceof Number) 30 | { 31 | Instant instant = Instant.ofEpochMilli(((Number) val).longValue()); 32 | return ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); 33 | } 34 | 35 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.util.ZonedDateTime"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/query/ParameterValue.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.query; 2 | 3 | import com.github.molcikas.photon.blueprints.table.ColumnBlueprint; 4 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 5 | import com.github.molcikas.photon.blueprints.table.TableValue; 6 | import com.github.molcikas.photon.converters.Converter; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.Getter; 9 | 10 | @EqualsAndHashCode(doNotUseGetters = true) 11 | public class ParameterValue 12 | { 13 | private final TableValue value; 14 | 15 | @Getter 16 | private final ColumnDataType dataType; 17 | 18 | @Getter 19 | private final Converter customSerializer; 20 | 21 | public ParameterValue(Object value, ColumnDataType dataType, Converter customSerializer) 22 | { 23 | this.value = new TableValue(value); 24 | this.dataType = dataType; 25 | this.customSerializer = customSerializer; 26 | } 27 | 28 | public ParameterValue(Object value, ColumnBlueprint columnBlueprint) 29 | { 30 | this.value = new TableValue(value); 31 | this.dataType = columnBlueprint.getColumnDataType(); 32 | this.customSerializer = columnBlueprint.getCustomSerializer(); 33 | } 34 | 35 | public Object getRawValue() 36 | { 37 | return value.getValue(); 38 | } 39 | } -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/DefaultEnumConverterFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | /** 4 | * Default implementation of {@link EnumConverterFactory}, 5 | * used by sql2o to convert a value from the database into an {@link Enum}. 6 | */ 7 | public class DefaultEnumConverterFactory implements EnumConverterFactory { 8 | public Converter newConverter(final Class enumType) { 9 | return new Converter() { 10 | @SuppressWarnings("unchecked") 11 | public E convert(Object val) throws ConverterException { 12 | if (val == null) { 13 | return null; 14 | } 15 | try { 16 | if (val instanceof String){ 17 | return (E)Enum.valueOf(enumType, val.toString()); 18 | } else if (val instanceof Number){ 19 | return enumType.getEnumConstants()[((Number)val).intValue()]; 20 | } 21 | } catch (Throwable t) { 22 | throw new ConverterException("Error converting value '" + val.toString() + "' to " + enumType.getName(), t); 23 | } 24 | throw new ConverterException("Cannot convert type '" + val.getClass().getName() + "' to an Enum"); 25 | } 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/product/ProductDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.product; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class ProductDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | return setupDatabase(photon); 13 | } 14 | 15 | public static Photon setupDatabase(Photon photon) 16 | { 17 | try(PhotonTransaction transaction = photon.beginTransaction()) 18 | { 19 | transaction.query("DROP TABLE IF EXISTS `product`").executeUpdate(); 20 | transaction.query("CREATE TABLE `product` (\n" + 21 | " `theProductId` int(11) NOT NULL AUTO_INCREMENT,\n" + 22 | " `numerator` int(11) NOT NULL,\n" + 23 | " `denominator` int(11) NOT NULL,\n" + 24 | " PRIMARY KEY (`theProductId`)\n" + 25 | ")").executeUpdate(); 26 | transaction.query("insert into `product` (`theProductId`, `numerator`, `denominator`) values (1, 2, 3)").executeUpdate(); 27 | 28 | transaction.commit(); 29 | } 30 | 31 | return photon; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/LocalDateTimeConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | import com.github.molcikas.photon.converters.ConverterException; 5 | 6 | import java.time.Instant; 7 | import java.time.LocalDateTime; 8 | import java.time.ZoneId; 9 | import java.util.Date; 10 | 11 | public class LocalDateTimeConverter implements Converter 12 | { 13 | public LocalDateTime convert(Object val) throws ConverterException 14 | { 15 | if (val == null) 16 | { 17 | return null; 18 | } 19 | 20 | if(LocalDateTime.class.isAssignableFrom(val.getClass())) 21 | { 22 | return (LocalDateTime) val; 23 | } 24 | 25 | if(Date.class.isAssignableFrom(val.getClass())) 26 | { 27 | Instant instant = Instant.ofEpochMilli(((Date) val).getTime()); 28 | return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); 29 | } 30 | 31 | if (val instanceof Number) 32 | { 33 | Instant instant = Instant.ofEpochMilli(((Number) val).longValue()); 34 | return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); 35 | } 36 | 37 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.util.LocalDateTime"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/BooleanConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: lars 6 | * Date: 6/1/13 7 | * Time: 10:54 PM 8 | * To change this template use File | Settings | File Templates. 9 | */ 10 | public class BooleanConverter implements Converter 11 | { 12 | 13 | public Boolean convert(Object val) throws ConverterException { 14 | if (val == null) return false; 15 | 16 | if (val instanceof Boolean) { 17 | return (Boolean) val; 18 | } 19 | 20 | if (val instanceof Number) { 21 | return ((Number)val).intValue() != 0; 22 | } 23 | 24 | if (val instanceof Character) { 25 | // cast to char is required to compile with java 8 26 | return (char)val =='Y' 27 | || (char)val =='T' 28 | || (char)val =='J'; 29 | } 30 | 31 | if (val instanceof String) { 32 | String strVal = ((String)val).trim(); 33 | return "Y".equalsIgnoreCase(strVal) || "YES".equalsIgnoreCase(strVal) || "TRUE".equalsIgnoreCase(strVal) || 34 | "T".equalsIgnoreCase(strVal) || "J".equalsIgnoreCase(strVal); 35 | } 36 | 37 | throw new ConverterException("Don't know how to convert type " + val.getClass().getName() + " to " + Boolean.class.getName()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/EntityFieldValueMapping.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Interface for custom mapping for getting and setting field values on an entity for a given database column. 7 | * 8 | * @param - The entity class type 9 | * @param - The field class type 10 | */ 11 | public interface EntityFieldValueMapping 12 | { 13 | /** 14 | * Get the field value from the entity instance that maps to the database column. 15 | * 16 | * @param entityInstance - The entity instance 17 | * @return - The field value 18 | */ 19 | F getFieldValue(E entityInstance); 20 | 21 | /** 22 | * Set the field value(s) on a given entity instance that maps to the database column. The value can be applied 23 | * directly to the entity instance, or the method can return a map of values that will be applied to the entity 24 | * instance (to avoid having to write reflection code directly in this method). 25 | * 26 | * @param entityInstance - The entity instance 27 | * @param fieldValue - The field value 28 | * @return - The field values to set on the entity. The key is the field name and the value is the field value. If 29 | * the value was applied directly to the entity instance, then null or an empty map can be returned. 30 | */ 31 | Map setFieldValue(E entityInstance, F fieldValue); 32 | } 33 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/someaggregate/SomeAggregateTests.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.someaggregate; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 6 | import com.github.molcikas.photon.tests.unit.entities.someaggregate.SomeAggregate; 7 | import com.github.molcikas.photon.tests.unit.entities.someaggregate.SomeClass; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | public class SomeAggregateTests 12 | { 13 | private Photon photon; 14 | 15 | @Before 16 | public void setupDatabase() 17 | { 18 | photon = SomeAggregateDbSetup.setupDatabase(); 19 | } 20 | 21 | @Test 22 | public void update_entityWithOnlyId_doesNothing() 23 | { 24 | registerAggregate(); 25 | 26 | try(PhotonTransaction transaction = photon.beginTransaction()) 27 | { 28 | SomeAggregate someAggregate = transaction 29 | .query(SomeAggregate.class) 30 | .fetchById(1); 31 | 32 | transaction.save(someAggregate); 33 | } 34 | } 35 | 36 | private void registerAggregate() 37 | { 38 | photon 39 | .registerAggregate(SomeAggregate.class) 40 | .withChild("fieldOne", SomeClass.class) 41 | .withForeignKeyToParent("someAggregateId", ColumnDataType.INTEGER) 42 | .addAsChild() 43 | .register(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/shape/Shape.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.shape; 2 | 3 | import lombok.EqualsAndHashCode; 4 | 5 | import java.util.List; 6 | 7 | @EqualsAndHashCode 8 | public class Shape 9 | { 10 | private Integer id; 11 | 12 | private Integer drawingId; 13 | 14 | private String type; 15 | 16 | private String color; 17 | 18 | private List colorHistory; 19 | 20 | public Integer getId() 21 | { 22 | return id; 23 | } 24 | 25 | public String getType() 26 | { 27 | return type; 28 | } 29 | 30 | public String getColor() 31 | { 32 | return color; 33 | } 34 | 35 | public List getColorHistory() 36 | { 37 | return colorHistory; 38 | } 39 | 40 | protected Shape() 41 | { 42 | } 43 | 44 | public Shape(Integer id, String type, String color, Integer drawingId) 45 | { 46 | this.id = id; 47 | this.type = type; 48 | this.color = color; 49 | this.drawingId = drawingId; 50 | } 51 | 52 | 53 | public Shape(Integer id, 54 | String type, 55 | String color, 56 | Integer drawingId, 57 | List colorHistory) 58 | { 59 | this.id = id; 60 | this.type = type; 61 | this.color = color; 62 | this.drawingId = drawingId; 63 | this.colorHistory = colorHistory; 64 | } 65 | 66 | public void setColor(String color) 67 | { 68 | this.color = color; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/MappedClassBlueprint.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import com.github.molcikas.photon.exceptions.PhotonException; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | public class MappedClassBlueprint 13 | { 14 | private final Class mappedClass; 15 | private final boolean includeAllFields; 16 | private final List includedFields; 17 | 18 | public Class getMappedClass() 19 | { 20 | return mappedClass; 21 | } 22 | 23 | public MappedClassBlueprint(Class mappedClass, boolean includeAllFields, List includedFields) 24 | { 25 | if(mappedClass == null) 26 | { 27 | throw new PhotonException("Mapped class cannot be null."); 28 | } 29 | 30 | this.mappedClass = mappedClass; 31 | this.includeAllFields = includeAllFields; 32 | this.includedFields = new ArrayList<>(includedFields != null ? includedFields : Collections.emptyList()); 33 | } 34 | 35 | public List getIncludedFields() 36 | { 37 | List fieldsToInclude = Arrays.asList(mappedClass.getDeclaredFields()); 38 | 39 | if(includeAllFields) 40 | { 41 | return fieldsToInclude; 42 | } 43 | 44 | return fieldsToInclude 45 | .stream() 46 | .filter(f -> includedFields.contains(f.getName())) 47 | .collect(Collectors.toList()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/query/PhotonSqlParameter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.query; 2 | 3 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 4 | import com.github.molcikas.photon.blueprints.table.TableBlueprintBuilder; 5 | import com.github.molcikas.photon.options.PhotonOptions; 6 | 7 | import java.util.Collection; 8 | 9 | public class PhotonSqlParameter 10 | { 11 | private final String name; 12 | private Object value; 13 | private ColumnDataType dataType; 14 | private boolean isCollection; 15 | 16 | public String getName() 17 | { 18 | return name; 19 | } 20 | 21 | public Object getValue() 22 | { 23 | return value; 24 | } 25 | 26 | public ColumnDataType getDataType() 27 | { 28 | return dataType; 29 | } 30 | 31 | public boolean isCollection() 32 | { 33 | return isCollection; 34 | } 35 | 36 | public PhotonSqlParameter(String name) 37 | { 38 | this.name = name; 39 | } 40 | 41 | public void assignValue(Object value, PhotonOptions photonOptions) 42 | { 43 | this.value = value; 44 | this.dataType = value != null ? TableBlueprintBuilder.defaultColumnDataTypeForField(value.getClass(), photonOptions).dataType : null; 45 | this.isCollection = value != null && Collection.class.isAssignableFrom(value.getClass()); 46 | } 47 | 48 | public void assignValue(Object value, ColumnDataType dataType) 49 | { 50 | this.value = value; 51 | this.dataType = dataType; 52 | this.isCollection = value != null && Collection.class.isAssignableFrom(value.getClass()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/blueprints/MyTableBlueprintTests.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.blueprints; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import com.github.molcikas.photon.datasource.GenericDataSource; 6 | import com.github.molcikas.photon.Photon; 7 | import com.github.molcikas.photon.exceptions.PhotonException; 8 | import com.github.molcikas.photon.tests.unit.entities.mytable.MyOtherTable; 9 | import com.github.molcikas.photon.tests.unit.entities.mytable.MyTable; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | public class MyTableBlueprintTests 14 | { 15 | @Test 16 | public void registerEntity_childWithNoForeignKeyToParent_ThrowsException() 17 | { 18 | Photon photon = new Photon(new GenericDataSource("", "", "")); 19 | 20 | try 21 | { 22 | photon.registerAggregate(MyTable.class) 23 | .withId("id") 24 | .withChild("myOtherTable", MyOtherTable.class) 25 | .withId("id") 26 | .addAsChild() 27 | .register(); 28 | 29 | Assert.fail("Failed to throw PhotonException."); 30 | } 31 | catch(PhotonException ex) 32 | { 33 | assertTrue(ex.getMessage().contains("foreign key")); 34 | } 35 | } 36 | 37 | // TODO: Aggregate root entity must have primary key. 38 | 39 | // TODO: Entity without a primary key field cannot have child entities. 40 | 41 | // TODO: Entity without a primary key field cannot have foreign key list field. 42 | 43 | // TODO: Cannot set withForeignKeyToParent to the primary key and also set primaryKeyAutoIncrement to true. 44 | } 45 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/recipe/RecipeInstruction.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.recipe; 2 | 3 | import java.util.Objects; 4 | import java.util.UUID; 5 | 6 | public class RecipeInstruction 7 | { 8 | private UUID recipeInstructionId; 9 | 10 | private int stepNumber; 11 | 12 | private String description; 13 | 14 | public UUID getRecipeInstructionId() 15 | { 16 | return recipeInstructionId; 17 | } 18 | 19 | public int getStepNumber() 20 | { 21 | return stepNumber; 22 | } 23 | 24 | public String getDescription() 25 | { 26 | return description; 27 | } 28 | 29 | private RecipeInstruction() 30 | { 31 | } 32 | 33 | public RecipeInstruction(UUID recipeInstructionId, int stepNumber, String description) 34 | { 35 | this.recipeInstructionId = recipeInstructionId; 36 | this.stepNumber = stepNumber; 37 | this.description = description; 38 | } 39 | 40 | public void setDescription(String description) 41 | { 42 | this.description = description; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) 47 | { 48 | if (this == o) return true; 49 | if (o == null || getClass() != o.getClass()) return false; 50 | RecipeInstruction that = (RecipeInstruction) o; 51 | return stepNumber == that.stepNumber && 52 | Objects.equals(recipeInstructionId, that.recipeInstructionId) && 53 | Objects.equals(description, that.description); 54 | } 55 | 56 | @Override 57 | public int hashCode() 58 | { 59 | return Objects.hash(recipeInstructionId, stepNumber, description); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /photon-core/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | description = "A micro ORM that gives developers control over the SQL executed while also providing an easy way to do basic CRUD operations on aggregates." 3 | archivesBaseName = "photon-core" 4 | 5 | dependencies { 6 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4' 7 | compile group: 'org.apache.commons', name: 'commons-collections4', version: '4.1' 8 | compile group: 'org.projectlombok', name: 'lombok', version: '1.16.16' 9 | 10 | // Database 11 | testCompile group: 'com.h2database', name: 'h2', version: '1.4.193' 12 | testCompile group: 'mysql', name: 'mysql-connector-java', version: '5.1.40' 13 | testCompile group: 'com.microsoft.sqlserver', name: 'sqljdbc4', version: '4.0' 14 | testCompile group: 'org.postgresql', name: 'postgresql', version: '9.4.1212' 15 | testCompile files('src/test/libs/ojdbc6.jar') 16 | 17 | // Unit testing 18 | testCompile group: 'junit', name: 'junit', version: '4.12' 19 | testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19' 20 | testCompile group: 'commons-io', name: 'commons-io', version: '2.5' 21 | } 22 | 23 | idea { 24 | module { 25 | iml.withXml { xmlFile -> 26 | def facetManager = xmlFile.asNode().component.find { it.@name == 'FacetManager' } as Node 27 | if (!facetManager) { 28 | facetManager = xmlFile.asNode().appendNode('component', [name: 'FacetManager']) 29 | } 30 | def builder = new NodeBuilder() 31 | def infinitestFacet = builder.facet(type: "Infinitest", name: 'Infinitest') { 32 | configuration {} 33 | } 34 | facetManager.append infinitestFacet 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /classes/test/photon-perf-test/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | com.github.molcikas.photon.perf.hibernate.RecipeEntity 8 | com.github.molcikas.photon.perf.hibernate.RecipeIngredientEntity 9 | com.github.molcikas.photon.perf.hibernate.RecipeInstructionEntity 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | com.github.molcikas.photon.perf.hibernate.RecipeEntity 8 | com.github.molcikas.photon.perf.hibernate.RecipeIngredientEntity 9 | com.github.molcikas.photon.perf.hibernate.RecipeInstructionEntity 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/integration/PhotonTestTable.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.integration; 2 | 3 | import java.time.ZonedDateTime; 4 | import java.util.Objects; 5 | import java.util.UUID; 6 | 7 | public class PhotonTestTable 8 | { 9 | private Integer id; 10 | private UUID uuidColumn; 11 | private ZonedDateTime dateColumn; 12 | private String varcharColumn; 13 | 14 | public int getId() 15 | { 16 | return id; 17 | } 18 | 19 | public UUID getUuidColumn() 20 | { 21 | return uuidColumn; 22 | } 23 | 24 | public ZonedDateTime getDateColumn() 25 | { 26 | return dateColumn; 27 | } 28 | 29 | public String getVarcharColumn() 30 | { 31 | return varcharColumn; 32 | } 33 | 34 | private PhotonTestTable() 35 | { 36 | } 37 | 38 | public PhotonTestTable(Integer id, UUID uuidColumn, ZonedDateTime dateColumn, String varcharColumn) 39 | { 40 | this.id = id; 41 | this.uuidColumn = uuidColumn; 42 | this.dateColumn = dateColumn; 43 | this.varcharColumn = varcharColumn; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) 48 | { 49 | if (this == o) return true; 50 | if (o == null || getClass() != o.getClass()) return false; 51 | PhotonTestTable that = (PhotonTestTable) o; 52 | return Objects.equals(id, that.id) && 53 | Objects.equals(uuidColumn, that.uuidColumn) && 54 | Objects.equals(dateColumn, that.dateColumn) && 55 | Objects.equals(varcharColumn, that.varcharColumn); 56 | } 57 | 58 | @Override 59 | public int hashCode() 60 | { 61 | return Objects.hash(id, uuidColumn, dateColumn, varcharColumn); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/hibernate/RecipeIngredientEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.hibernate; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | import javax.persistence.Table; 7 | import java.util.UUID; 8 | 9 | @Entity 10 | @Table(name = "recipeingredient") 11 | public class RecipeIngredientEntity 12 | { 13 | @Id 14 | @Column(columnDefinition = "BINARY(16)", length = 16) 15 | public UUID recipeIngredientId; 16 | 17 | @Column(columnDefinition = "BINARY(16)", length = 16) 18 | public UUID recipeId; 19 | 20 | @Column 21 | public boolean isRequired; 22 | 23 | @Column 24 | public String quantity; 25 | 26 | @Column 27 | public String quantityUnit; 28 | 29 | @Column 30 | public String quantityDetail; 31 | 32 | @Column 33 | public String name; 34 | 35 | @Column 36 | public String preparation; 37 | 38 | @Column 39 | public int orderBy; 40 | 41 | public void setName(String name) 42 | { 43 | this.name = name; 44 | } 45 | 46 | protected RecipeIngredientEntity() 47 | { 48 | } 49 | 50 | public RecipeIngredientEntity(UUID recipeIngredientId, UUID recipeId, boolean isRequired, String quantity, String quantityUnit, String quantityDetail, String name, String preparation, int orderBy) 51 | { 52 | this.recipeIngredientId = recipeIngredientId; 53 | this.recipeId = recipeId; 54 | this.isRequired = isRequired; 55 | this.quantity = quantity; 56 | this.quantityUnit = quantityUnit; 57 | this.quantityDetail = quantityDetail; 58 | this.name = name; 59 | this.preparation = preparation; 60 | this.orderBy = orderBy; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/TableValue.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | import lombok.Getter; 4 | 5 | import javax.xml.bind.DatatypeConverter; 6 | import java.util.Arrays; 7 | import java.util.Objects; 8 | 9 | public class TableValue 10 | { 11 | @Getter 12 | private final Object value; 13 | 14 | public TableValue(Object value) 15 | { 16 | this.value = value; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) 21 | { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | TableValue other = (TableValue) o; 25 | 26 | if (Objects.equals(value, other.value)) 27 | { 28 | return true; 29 | } 30 | 31 | return value instanceof byte[] && other.value instanceof byte[] && Arrays.equals((byte[]) value, (byte[]) other.value); 32 | } 33 | 34 | @Override 35 | public int hashCode() 36 | { 37 | if (value instanceof byte[]) 38 | { 39 | byte[] keyArray = (byte[]) value; 40 | int hash = keyArray.length; 41 | int position = 0; 42 | for (byte b : keyArray) 43 | { 44 | hash += b << (position % 4); 45 | position++; 46 | } 47 | return hash; 48 | } 49 | 50 | return Objects.hash(value); 51 | } 52 | 53 | @Override 54 | public String toString() 55 | { 56 | if(value == null) 57 | { 58 | return "(null)"; 59 | } 60 | 61 | if(value instanceof byte[]) 62 | { 63 | return "(" + DatatypeConverter.printHexBinary((byte[]) value) + ")"; 64 | } 65 | 66 | return "(" + value + ")"; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/DateConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | import com.github.molcikas.photon.converters.ConverterException; 5 | 6 | import java.time.*; 7 | import java.util.Date; 8 | 9 | public class DateConverter implements Converter 10 | { 11 | public Date convert(Object val) throws ConverterException 12 | { 13 | if (val == null) 14 | { 15 | return null; 16 | } 17 | 18 | if(Date.class.isAssignableFrom(val.getClass())) 19 | { 20 | return new Date(((Date)val).getTime()); 21 | } 22 | 23 | if(ZonedDateTime.class.isAssignableFrom(val.getClass())) 24 | { 25 | return new Date(((ZonedDateTime)val).toEpochSecond() * 1000); 26 | } 27 | 28 | if(LocalDate.class.isAssignableFrom(val.getClass())) 29 | { 30 | LocalDate localDate = (LocalDate) val; 31 | return new Date(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond() * 1000); 32 | } 33 | 34 | if(LocalDateTime.class.isAssignableFrom(val.getClass())) 35 | { 36 | LocalDateTime localDateTime = (LocalDateTime) val; 37 | return new Date(localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000); 38 | } 39 | 40 | if(Instant.class.isAssignableFrom(val.getClass())) 41 | { 42 | return new Date(((Instant)val).getEpochSecond() * 1000); 43 | } 44 | 45 | if (val instanceof Number) 46 | { 47 | return new Date(((Number) val).longValue()); 48 | } 49 | 50 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.util.Date"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/StringConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.io.IOException; 4 | import java.io.Reader; 5 | import java.sql.Clob; 6 | import java.sql.SQLException; 7 | 8 | /** 9 | * Used by sql2o to convert a value from the database into a {@link String}. 10 | */ 11 | public class StringConverter implements Converter 12 | { 13 | 14 | public String convert(Object val) throws ConverterException { 15 | if (val == null){ 16 | return null; 17 | } 18 | 19 | if (val instanceof Clob) { 20 | Clob clobVal = (Clob)val; 21 | try 22 | { 23 | try { 24 | return clobVal.getSubString(1, (int)clobVal.length()); 25 | } catch (SQLException e) { 26 | throw new ConverterException("error converting clob to String", e); 27 | } 28 | } finally { 29 | try { 30 | clobVal.free(); 31 | } catch (Throwable ignore) { 32 | //ignore 33 | } 34 | } 35 | } 36 | 37 | if(val instanceof Reader){ 38 | Reader reader = (Reader) val; 39 | try { 40 | try { 41 | return IOUtils.toString(reader); 42 | } catch (IOException e) { 43 | throw new ConverterException("error converting reader to String", e); 44 | } 45 | } finally { 46 | try { 47 | reader.close(); 48 | } catch (Throwable ignore) { 49 | // ignore 50 | } 51 | } 52 | } 53 | 54 | if(val instanceof Enum) { 55 | return ((Enum) val).name(); 56 | } 57 | 58 | return val.toString().trim(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/date/TimestampConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.date; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | import com.github.molcikas.photon.converters.ConverterException; 5 | 6 | import java.sql.Timestamp; 7 | import java.time.*; 8 | import java.util.Date; 9 | 10 | public class TimestampConverter implements Converter 11 | { 12 | public Timestamp convert(Object val) throws ConverterException 13 | { 14 | if (val == null) 15 | { 16 | return null; 17 | } 18 | 19 | if(Date.class.isAssignableFrom(val.getClass())) 20 | { 21 | return new Timestamp(((Date)val).getTime()); 22 | } 23 | 24 | if(ZonedDateTime.class.isAssignableFrom(val.getClass())) 25 | { 26 | return new Timestamp(((ZonedDateTime)val).toEpochSecond() * 1000); 27 | } 28 | 29 | if(LocalDate.class.isAssignableFrom(val.getClass())) 30 | { 31 | LocalDate localDate = (LocalDate) val; 32 | return new Timestamp(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toEpochSecond() * 1000); 33 | } 34 | 35 | if(LocalDateTime.class.isAssignableFrom(val.getClass())) 36 | { 37 | LocalDateTime localDateTime = (LocalDateTime) val; 38 | return new Timestamp(localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond() * 1000); 39 | } 40 | 41 | if(Instant.class.isAssignableFrom(val.getClass())) 42 | { 43 | return new Timestamp(((Instant)val).getEpochSecond() * 1000); 44 | } 45 | 46 | if (val instanceof Number) 47 | { 48 | return new Timestamp(((Number) val).longValue()); 49 | } 50 | 51 | throw new ConverterException("Cannot convert type " + val.getClass().toString() + " to java.sql.Timestamp"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/number/NumberConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters.number; 2 | 3 | import com.github.molcikas.photon.converters.Converter; 4 | 5 | /** 6 | * Base class for numeric converters. 7 | */ 8 | public abstract class NumberConverter implements Converter 9 | { 10 | private boolean isPrimitive; 11 | 12 | public NumberConverter(boolean primitive) { 13 | isPrimitive = primitive; 14 | } 15 | 16 | public V convert(Object val) { 17 | if (val == null) { 18 | return isPrimitive ? convertNumberValue(0) : null; 19 | } 20 | 21 | // val.getClass().isPrimitive() is ALWAYS false 22 | // since boxing (i.e. Object val=(int)1;) 23 | // changes type from Integet.TYPE to Integer.class 24 | // learn 2 java :) 25 | 26 | else if (/*val.getClass().isPrimitive() || */val instanceof Number ) { 27 | return convertNumberValue((Number)val); 28 | } 29 | else if (val instanceof String) { 30 | String stringVal = ((String)val).trim(); 31 | stringVal = stringVal.isEmpty() ? null : stringVal; 32 | 33 | if (stringVal == null) { 34 | return isPrimitive ? convertNumberValue(0) : null; 35 | } 36 | 37 | return convertStringValue(stringVal); 38 | } 39 | else if(val instanceof Enum) { 40 | int enumVal = ((Enum) val).ordinal(); 41 | return convertNumberValue(enumVal); 42 | } 43 | else { 44 | throw new IllegalArgumentException("Cannot convert type " + val.getClass().toString() + " to " + getTypeDescription()); 45 | } 46 | } 47 | 48 | protected abstract V convertNumberValue(Number val); 49 | 50 | protected abstract V convertStringValue(String val); 51 | 52 | protected abstract String getTypeDescription(); 53 | } 54 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/photon/RecipeIngredient.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.photon; 2 | 3 | import java.util.UUID; 4 | 5 | public class RecipeIngredient 6 | { 7 | private UUID recipeIngredientId; 8 | 9 | private boolean isRequired; 10 | 11 | private String quantity; 12 | 13 | private String quantityUnit; 14 | 15 | private String quantityDetail; 16 | 17 | private String name; 18 | 19 | private String preparation; 20 | 21 | private Integer orderBy; 22 | 23 | public UUID getRecipeIngredientId() 24 | { 25 | return recipeIngredientId; 26 | } 27 | 28 | public boolean isRequired() 29 | { 30 | return isRequired; 31 | } 32 | 33 | public String getQuantity() 34 | { 35 | return quantity; 36 | } 37 | 38 | public String getQuantityUnit() 39 | { 40 | return quantityUnit; 41 | } 42 | 43 | public String getQuantityDetail() 44 | { 45 | return quantityDetail; 46 | } 47 | 48 | public String getName() 49 | { 50 | return name; 51 | } 52 | 53 | public String getPreparation() 54 | { 55 | return preparation; 56 | } 57 | 58 | public Integer getOrderBy() 59 | { 60 | return orderBy; 61 | } 62 | 63 | public void setName(String name) 64 | { 65 | this.name = name; 66 | } 67 | 68 | private RecipeIngredient() 69 | { 70 | } 71 | 72 | public RecipeIngredient(UUID recipeIngredientId, boolean isRequired, String quantity, String quantityUnit, String quantityDetail, String name, String preparation, Integer orderBy) 73 | { 74 | this.recipeIngredientId = recipeIngredientId; 75 | this.isRequired = isRequired; 76 | this.quantity = quantity; 77 | this.quantityUnit = quantityUnit; 78 | this.quantityDetail = quantityDetail; 79 | this.name = name; 80 | this.preparation = preparation; 81 | this.orderBy = orderBy; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/sqlbuilders/SqlJoinClauseBuilderService.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.sqlbuilders; 2 | 3 | import com.github.molcikas.photon.blueprints.table.TableBlueprint; 4 | 5 | import java.util.*; 6 | 7 | public final class SqlJoinClauseBuilderService 8 | { 9 | public static void buildChildToParentJoinClauseSql( 10 | StringBuilder sqlBuilder, 11 | TableBlueprint tableBlueprint, 12 | boolean alwaysUseInnerJoins) 13 | { 14 | while(tableBlueprint.getParentTableBlueprint() != null) 15 | { 16 | sqlBuilder.append(String.format("%n%s [%s] ON [%s].[%s] = [%s].[%s]", 17 | alwaysUseInnerJoins ? "JOIN" : tableBlueprint.getJoinType().getJoinSql(), 18 | tableBlueprint.getParentTableBlueprint().getTableName(), 19 | tableBlueprint.getParentTableBlueprint().getTableName(), 20 | tableBlueprint.getParentTableBlueprint().getPrimaryKeyColumnName(), 21 | tableBlueprint.getTableName(), 22 | tableBlueprint.getForeignKeyToParentColumn().getColumnName() 23 | )); 24 | tableBlueprint = tableBlueprint.getParentTableBlueprint(); 25 | } 26 | } 27 | 28 | public static void buildParentToEachChildJoinClauseSql( 29 | StringBuilder sqlBuilder, 30 | TableBlueprint parentTableBlueprint, 31 | List childTableBlueprints) 32 | { 33 | for(TableBlueprint childTableBlueprint : childTableBlueprints) 34 | { 35 | sqlBuilder.append(String.format("%n%s [%s] ON [%s].[%s] = [%s].[%s]", 36 | childTableBlueprint.getJoinType().getJoinSql(), 37 | childTableBlueprint.getTableName(), 38 | childTableBlueprint.getTableName(), 39 | childTableBlueprint.getForeignKeyToParentColumn().getColumnName(), 40 | parentTableBlueprint.getTableName(), 41 | parentTableBlueprint.getPrimaryKeyColumnName() 42 | )); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/converters/ByteArrayConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.converters; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.ByteBuffer; 6 | import java.sql.Blob; 7 | import java.sql.SQLException; 8 | import java.util.UUID; 9 | 10 | public class ByteArrayConverter implements Converter 11 | { 12 | 13 | public byte[] convert(Object val) throws ConverterException { 14 | if (val == null) return null; 15 | 16 | if (val instanceof Blob) { 17 | Blob b = (Blob)val; 18 | InputStream stream=null; 19 | try { 20 | try { 21 | stream = b.getBinaryStream(); 22 | return IOUtils.toByteArray(stream); 23 | } finally { 24 | if(stream!=null) { 25 | try { 26 | stream.close(); 27 | } catch (Throwable ignore){ 28 | // ignore stream.close errors 29 | } 30 | } 31 | try { 32 | b.free(); 33 | } catch (Throwable ignore){ 34 | // ignore blob.free errors 35 | } 36 | } 37 | } catch (SQLException e) { 38 | throw new ConverterException("Error converting Blob to byte[]", e); 39 | } catch (IOException e) { 40 | throw new ConverterException("Error converting Blob to byte[]", e); 41 | } 42 | } 43 | 44 | if (val instanceof byte[]){ 45 | return (byte[])val; 46 | } 47 | 48 | if(val instanceof UUID) 49 | { 50 | UUID uuid = (UUID) val; 51 | ByteBuffer bb = ByteBuffer.wrap(new byte[16]); 52 | bb.putLong(uuid.getMostSignificantBits()); 53 | bb.putLong(uuid.getLeastSignificantBits()); 54 | return bb.array(); 55 | } 56 | 57 | throw new RuntimeException("could not convert " + val.getClass().getName() + " to byte[]"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/ReflectionTest.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf; 2 | 3 | import org.apache.commons.lang3.time.StopWatch; 4 | import org.junit.Test; 5 | import com.github.molcikas.photon.perf.photon.Recipe; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class ReflectionTest 10 | { 11 | private final int TEST_RUNS = 10; 12 | private final int TEST_ITERATIONS = 10000000; 13 | 14 | @Test 15 | public void testReflectionNoCache() 16 | { 17 | try 18 | { 19 | for(int t = 0; t < TEST_RUNS; t++) 20 | { 21 | Recipe recipe = new Recipe(); 22 | 23 | StopWatch stopWatch = new StopWatch(); 24 | stopWatch.start(); 25 | for (int i = 0; i < TEST_ITERATIONS; i++) 26 | { 27 | Field field = Recipe.class.getDeclaredField("name"); 28 | field.setAccessible(true); 29 | field.set(recipe, "myname" + i); 30 | } 31 | long finishTime = stopWatch.getNanoTime(); 32 | System.out.println(String.format("Finished after %s ms with avg set taking %s ns.", finishTime / 1000000, finishTime / TEST_ITERATIONS)); 33 | } 34 | } 35 | catch(Exception ex) 36 | { 37 | throw new RuntimeException(ex); 38 | } 39 | } 40 | 41 | @Test 42 | public void testReflectionWithCache() 43 | { 44 | try 45 | { 46 | for(int t = 0; t < TEST_RUNS; t++) 47 | { 48 | Recipe recipe = new Recipe(); 49 | Field field = Recipe.class.getDeclaredField("name"); 50 | field.setAccessible(true); 51 | 52 | StopWatch stopWatch = new StopWatch(); 53 | stopWatch.start(); 54 | for (int i = 0; i < TEST_ITERATIONS; i++) 55 | { 56 | field.set(recipe, "myname" + i); 57 | } 58 | long finishTime = stopWatch.getNanoTime(); 59 | System.out.println(String.format("Finished after %s ms with avg set taking %s ns.", finishTime / 1000000, finishTime / TEST_ITERATIONS)); 60 | } 61 | } 62 | catch(Exception ex) 63 | { 64 | throw new RuntimeException(ex); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/datasource/ExistingConnectionDataSource.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.datasource; 2 | 3 | import javax.sql.DataSource; 4 | import java.io.PrintWriter; 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.SQLException; 8 | import java.sql.SQLFeatureNotSupportedException; 9 | 10 | /** 11 | * A DataSource that always returns an existing provided connection. This is useful if Photon is being used alongside 12 | * another ORM. If photon is strictly used for queries and not updates, wrap the connection with ReadOnlyConnection so 13 | * that Photon does not ever modify the state of the connection. 14 | */ 15 | public class ExistingConnectionDataSource implements DataSource 16 | { 17 | private Connection connection; 18 | 19 | public ExistingConnectionDataSource() 20 | { 21 | } 22 | 23 | public ExistingConnectionDataSource(Connection connection) 24 | { 25 | this.connection = connection; 26 | } 27 | 28 | public Connection getConnection() throws SQLException 29 | { 30 | return connection; 31 | } 32 | 33 | public Connection getConnection(String username, String password) throws SQLException 34 | { 35 | return connection; 36 | } 37 | 38 | public PrintWriter getLogWriter() throws SQLException 39 | { 40 | return DriverManager.getLogWriter(); 41 | } 42 | 43 | public void setLogWriter(PrintWriter printWriter) throws SQLException 44 | { 45 | DriverManager.setLogWriter(printWriter); 46 | } 47 | 48 | public void setLoginTimeout(int i) throws SQLException 49 | { 50 | DriverManager.setLoginTimeout(i); 51 | } 52 | 53 | public int getLoginTimeout() throws SQLException 54 | { 55 | return DriverManager.getLoginTimeout(); 56 | } 57 | 58 | public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException 59 | { 60 | throw new SQLFeatureNotSupportedException(); 61 | } 62 | 63 | public T unwrap(Class tClass) throws SQLException 64 | { 65 | throw new SQLFeatureNotSupportedException(); 66 | } 67 | 68 | public boolean isWrapperFor(Class aClass) throws SQLException 69 | { 70 | return false; 71 | } 72 | 73 | public void setConnection(Connection connection) 74 | { 75 | this.connection = connection; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/hibernate/RecipeEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.hibernate; 2 | 3 | import javax.persistence.*; 4 | import java.util.Set; 5 | import java.util.UUID; 6 | 7 | @Entity 8 | @Table(name = "recipe") 9 | public class RecipeEntity 10 | { 11 | @Id 12 | @Column(columnDefinition = "BINARY(16)", length = 16) 13 | public UUID recipeId; 14 | 15 | @Column 16 | public String name; 17 | 18 | @Column 19 | public String description; 20 | 21 | @Column 22 | public int prepTime; 23 | 24 | @Column 25 | public int cookTime; 26 | 27 | @Column 28 | public int servings; 29 | 30 | @Column 31 | public boolean isVegetarian; 32 | 33 | @Column 34 | public boolean isVegan; 35 | 36 | @Column 37 | public boolean isPublished; 38 | 39 | @Column 40 | public String credit; 41 | 42 | @OneToMany(mappedBy = "recipeId", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 43 | @OrderBy("name ASC") 44 | public Set ingredients; 45 | 46 | @OneToMany(mappedBy = "recipeId", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 47 | @OrderBy("stepNumber ASC") 48 | public Set instructions; 49 | 50 | public Set getIngredients() 51 | { 52 | return ingredients; 53 | } 54 | 55 | public Set getInstructions() 56 | { 57 | return instructions; 58 | } 59 | 60 | public void setName(String name) 61 | { 62 | this.name = name; 63 | } 64 | 65 | protected RecipeEntity() 66 | { 67 | } 68 | 69 | public RecipeEntity(UUID recipeId, String name, String description, int prepTime, int cookTime, int servings, boolean isVegetarian, boolean isVegan, boolean isPublished, String credit, Set ingredients, Set instructions) 70 | { 71 | this.recipeId = recipeId; 72 | this.name = name; 73 | this.description = description; 74 | this.prepTime = prepTime; 75 | this.cookTime = cookTime; 76 | this.servings = servings; 77 | this.isVegetarian = isVegetarian; 78 | this.isVegan = isVegan; 79 | this.isPublished = isPublished; 80 | this.credit = credit; 81 | this.ingredients = ingredients; 82 | this.instructions = instructions; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/JoinedTableBlueprintBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | import com.github.molcikas.photon.blueprints.entity.EntityBlueprintBuilder; 4 | import com.github.molcikas.photon.blueprints.entity.FieldBlueprint; 5 | import com.github.molcikas.photon.exceptions.PhotonException; 6 | import com.github.molcikas.photon.options.PhotonOptions; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import java.util.List; 10 | 11 | public class JoinedTableBlueprintBuilder extends TableBlueprintBuilder 12 | { 13 | public JoinedTableBlueprintBuilder( 14 | Class entityClass, 15 | String tableName, 16 | JoinType joinType, 17 | EntityBlueprintBuilder entityBlueprintBuilder, 18 | PhotonOptions photonOptions) 19 | { 20 | super(entityClass, tableName, joinType, entityBlueprintBuilder, photonOptions); 21 | } 22 | 23 | @Override 24 | public TableBlueprintBuilder withParentTable(String parentTableName) 25 | { 26 | throw new PhotonException("Cannot call withParentTable() on a joined table builder."); 27 | } 28 | 29 | @Override 30 | public TableBlueprintBuilder withForeignKeyToParent(String foreignKeyToParent) 31 | { 32 | throw new PhotonException("Cannot call withForeignKeyToParent() on a joined table builder."); 33 | } 34 | 35 | @Override 36 | public TableBlueprintBuilder withForeignKeyToParent(String foreignKeyToParent, ColumnDataType columnDataType) 37 | { 38 | throw new PhotonException("Cannot call withForeignKeyToParent() on a joined table builder."); 39 | } 40 | 41 | @Override 42 | public TableBlueprint build( 43 | boolean isSimpleEntity, 44 | List fields, 45 | List parentEntityTables, 46 | TableBlueprint mainTableBlueprint, 47 | List joinedTableBuilders) 48 | { 49 | if(StringUtils.isBlank(idFieldName)) 50 | { 51 | idFieldName = determineDefaultIdFieldName(fields); 52 | } 53 | 54 | this.parentTableName = parentEntityTables.get(0); 55 | this.foreignKeyToParent = idFieldName; 56 | 57 | if(isSimpleEntity) 58 | { 59 | throw new PhotonException("Simple entity with main table '%s' cannot have joined tables.", parentTableName); 60 | } 61 | 62 | return super.build(false, fields, parentEntityTables, mainTableBlueprint, joinedTableBuilders); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/fieldtest/FieldTestDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.fieldtest; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 6 | import com.github.molcikas.photon.tests.unit.entities.fieldtest.FieldTest; 7 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 8 | 9 | public class FieldTestDbSetup 10 | { 11 | public static Photon setupDatabase() 12 | { 13 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 14 | 15 | try(PhotonTransaction transaction = photon.beginTransaction()) 16 | { 17 | transaction.query("DROP TABLE IF EXISTS `fieldtest`").executeUpdate(); 18 | transaction.query("CREATE TABLE `fieldtest` (\n" + 19 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 20 | " `date` DATETIME,\n" + 21 | " `zonedDateTime` DATETIME,\n" + 22 | " `localDate` DATETIME,\n" + 23 | " `localDateTime` DATETIME,\n" + 24 | " `instant` DATETIME,\n" + 25 | " `testEnumNumber` int(11),\n" + 26 | " `testEnumString` VARCHAR(255),\n" + 27 | " PRIMARY KEY (`id`)\n" + 28 | ")").executeUpdate(); 29 | transaction.query("insert into `fieldtest` (`id`, `date`, `zonedDateTime`, `localDate`, `localDateTime`, `instant`, `testEnumNumber`, `testEnumString`) " + 30 | "values (1, PARSEDATETIME('2017-03-19 09-28-17', 'yyyy-MM-dd HH-mm-ss'), PARSEDATETIME('2017-03-19 09-28-18', 'yyyy-MM-dd HH-mm-ss'), PARSEDATETIME('2017-03-19 09-28-19', 'yyyy-MM-dd HH-mm-ss'), PARSEDATETIME('2017-03-19 09-28-20', 'yyyy-MM-dd HH-mm-ss'), PARSEDATETIME('2017-03-19 09-28-21', 'yyyy-MM-dd HH-mm-ss'), 0, 'VALUE_ONE')").executeUpdate(); 31 | transaction.query("insert into `fieldtest` (`id`, `date`, `zonedDateTime`, `localDate`, `localDateTime`, `instant`, `testEnumNumber`, `testEnumString`) " + 32 | "values (2, NULL, NULL, NULL, NULL, NULL, NULL, NULL)").executeUpdate(); 33 | transaction.commit(); 34 | } 35 | 36 | return photon; 37 | } 38 | 39 | public static void registerAggregate(Photon photon) 40 | { 41 | photon.registerAggregate(FieldTest.class) 42 | .withId("id") 43 | .withDatabaseColumn("testEnumString", ColumnDataType.VARCHAR) 44 | .register(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/recipe/RecipeIngredient.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.recipe; 2 | 3 | import org.apache.commons.lang3.math.Fraction; 4 | 5 | import java.util.Objects; 6 | 7 | public class RecipeIngredient 8 | { 9 | private boolean isRequired; 10 | 11 | private Fraction quantity; 12 | 13 | private String quantityUnit; 14 | 15 | private String quantityDetail; 16 | 17 | private String name; 18 | 19 | private String preparation; 20 | 21 | private Integer orderBy; 22 | 23 | public boolean isRequired() 24 | { 25 | return isRequired; 26 | } 27 | 28 | public Fraction getQuantity() 29 | { 30 | return quantity; 31 | } 32 | 33 | public String getQuantityUnit() 34 | { 35 | return quantityUnit; 36 | } 37 | 38 | public String getQuantityDetail() 39 | { 40 | return quantityDetail; 41 | } 42 | 43 | public String getName() 44 | { 45 | return name; 46 | } 47 | 48 | public String getPreparation() 49 | { 50 | return preparation; 51 | } 52 | 53 | public Integer getOrderBy() 54 | { 55 | return orderBy; 56 | } 57 | 58 | private RecipeIngredient() 59 | { 60 | } 61 | 62 | public RecipeIngredient(boolean isRequired, Fraction quantity, String quantityUnit, String quantityDetail, String name, String preparation, Integer orderBy) 63 | { 64 | this.isRequired = isRequired; 65 | this.quantity = quantity; 66 | this.quantityUnit = quantityUnit; 67 | this.quantityDetail = quantityDetail; 68 | this.name = name; 69 | this.preparation = preparation; 70 | this.orderBy = orderBy; 71 | } 72 | 73 | @Override 74 | public boolean equals(Object o) 75 | { 76 | if (this == o) return true; 77 | if (o == null || getClass() != o.getClass()) return false; 78 | RecipeIngredient that = (RecipeIngredient) o; 79 | return isRequired == that.isRequired && 80 | Objects.equals(quantity, that.quantity) && 81 | Objects.equals(quantityUnit, that.quantityUnit) && 82 | Objects.equals(quantityDetail, that.quantityDetail) && 83 | Objects.equals(name, that.name) && 84 | Objects.equals(preparation, that.preparation) && 85 | Objects.equals(orderBy, that.orderBy); 86 | } 87 | 88 | @Override 89 | public int hashCode() 90 | { 91 | return Objects.hash(isRequired, quantity, quantityUnit, quantityDetail, name, preparation, orderBy); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/recipe/RecipeDeleteTests.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.recipe; 2 | 3 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import com.github.molcikas.photon.Photon; 7 | import com.github.molcikas.photon.PhotonTransaction; 8 | import com.github.molcikas.photon.tests.unit.entities.recipe.Recipe; 9 | import com.github.molcikas.photon.tests.unit.entities.recipe.RecipeIngredient; 10 | import com.github.molcikas.photon.tests.unit.entities.recipe.RecipeInstruction; 11 | 12 | import java.util.UUID; 13 | 14 | import static org.junit.Assert.assertNull; 15 | 16 | public class RecipeDeleteTests 17 | { 18 | private Photon photon; 19 | 20 | @Before 21 | public void setupDatabase() 22 | { 23 | photon = RecipeDbSetup.setupDatabase(); 24 | } 25 | 26 | @Test 27 | public void aggregate_delete_existingRecipe_deletesRecipe() 28 | { 29 | registerRecipeAggregate(); 30 | 31 | try (PhotonTransaction transaction = photon.beginTransaction()) 32 | { 33 | Recipe recipe1 = transaction.query(Recipe.class).fetchById(UUID.fromString("3E04169A-A9B6-11E6-AB83-0A0027000010")); 34 | 35 | transaction.delete(recipe1); 36 | transaction.commit(); 37 | } 38 | 39 | try (PhotonTransaction transaction = photon.beginTransaction()) 40 | { 41 | Recipe recipe1 = transaction.query(Recipe.class).fetchById(UUID.fromString("3E04169A-A9B6-11E6-AB83-0A0027000010")); 42 | 43 | assertNull(recipe1); 44 | transaction.commit(); 45 | } 46 | } 47 | 48 | private void registerRecipeAggregate() 49 | { 50 | registerRecipeAggregate("recipeingredient.orderBy"); 51 | } 52 | 53 | private void registerRecipeAggregate(String orderBySql) 54 | { 55 | photon.registerAggregate(Recipe.class) 56 | .withId("recipeId") 57 | .withChild("instructions", RecipeInstruction.class) 58 | .withId("recipeInstructionId", ColumnDataType.BINARY) 59 | .withForeignKeyToParent("recipeId") 60 | .withDatabaseColumn("recipeId", ColumnDataType.BINARY) 61 | .withOrderBySql("stepNumber") 62 | .addAsChild() 63 | .withChild("ingredients", RecipeIngredient.class) 64 | .withId("recipeIngredientId") 65 | .withForeignKeyToParent("recipeId") 66 | .withDatabaseColumn("recipeIngredientId", ColumnDataType.BINARY) 67 | .withDatabaseColumn("recipeId", ColumnDataType.BINARY) 68 | .withOrderBySql(orderBySql) 69 | .addAsChild() 70 | .register(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/mytable/MyTableDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.mytable; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class MyTableDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | return setupDatabase(photon); 13 | } 14 | 15 | public static Photon setupDatabase(Photon photon) 16 | { 17 | try(PhotonTransaction transaction = photon.beginTransaction()) 18 | { 19 | transaction.query("DROP TABLE IF EXISTS `mytable`").executeUpdate(); 20 | transaction.query("CREATE TABLE `mytable` (\n" + 21 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 22 | " `myvalue` varchar(255) DEFAULT 'oops',\n" + 23 | " `version` int(11) NOT NULL DEFAULT 1,\n" + 24 | " PRIMARY KEY (`id`)\n" + 25 | ")").executeUpdate(); 26 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (1, 'my1dbvalue')").executeUpdate(); 27 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (2, 'my2dbvalue')").executeUpdate(); 28 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (3, 'my3dbvalue')").executeUpdate(); 29 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (4, 'my4dbvalue')").executeUpdate(); 30 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (5, 'my5dbvalue')").executeUpdate(); 31 | transaction.query("insert into `mytable` (`id`, `myvalue`) values (6, NULL)").executeUpdate(); 32 | 33 | transaction.query("DROP TABLE IF EXISTS `myothertable`").executeUpdate(); 34 | transaction.query("CREATE TABLE `myothertable` (\n" + 35 | " `id` int(11) NOT NULL,\n" + 36 | " `myothervalue` varchar(255) DEFAULT 'oops',\n" + 37 | " PRIMARY KEY (`id`),\n" + 38 | " CONSTRAINT `MyOtherTable_MyTable` FOREIGN KEY (`id`) REFERENCES `mytable` (`id`)\n" + 39 | ")").executeUpdate(); 40 | transaction.query("insert into `myothertable` (`id`, `myothervalue`) values (3, 'my3otherdbvalue')").executeUpdate(); 41 | transaction.query("insert into `myothertable` (`id`, `myothervalue`) values (4, 'my4otherdbvalue')").executeUpdate(); 42 | transaction.query("insert into `myothertable` (`id`, `myothervalue`) values (5, 'my5otherdbvalue')").executeUpdate(); 43 | 44 | transaction.commit(); 45 | } 46 | 47 | return photon; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/datasource/GenericDataSource.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.datasource; 2 | 3 | import javax.sql.DataSource; 4 | import java.io.PrintWriter; 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.SQLException; 8 | import java.sql.SQLFeatureNotSupportedException; 9 | import java.util.Properties; 10 | 11 | 12 | public class GenericDataSource implements DataSource { 13 | 14 | private final String url; 15 | private final Properties properties; 16 | 17 | public GenericDataSource(String url, String user, String password) { 18 | 19 | if (!url.startsWith("jdbc")){ 20 | url = "jdbc:" + url; 21 | } 22 | 23 | this.url = url; 24 | this.properties = new Properties(); 25 | set(properties,user,password); 26 | } 27 | 28 | private void set(Properties info, String user, String password) { 29 | if (user != null) { 30 | info.put("user", user); 31 | } 32 | if (password != null) { 33 | info.put("password", password); 34 | } 35 | } 36 | 37 | public GenericDataSource(String url, Properties properties) { 38 | 39 | if (!url.startsWith("jdbc")){ 40 | url = "jdbc:" + url; 41 | } 42 | 43 | this.url = url; 44 | this.properties = properties; 45 | } 46 | 47 | public String getUrl() { 48 | return url; 49 | } 50 | 51 | public String getUser() { 52 | return properties.getProperty("user"); 53 | } 54 | 55 | public String getPassword() { 56 | return properties.getProperty("password"); 57 | } 58 | 59 | public Connection getConnection() throws SQLException { 60 | return DriverManager.getConnection(this.getUrl(), properties); 61 | } 62 | 63 | public Connection getConnection(String username, String password) throws SQLException { 64 | Properties info = new Properties(this.properties); 65 | set(info,username,password); 66 | return DriverManager.getConnection(this.getUrl(), info); 67 | } 68 | 69 | public PrintWriter getLogWriter() throws SQLException { 70 | return DriverManager.getLogWriter(); 71 | } 72 | 73 | public void setLogWriter(PrintWriter printWriter) throws SQLException { 74 | DriverManager.setLogWriter(printWriter); 75 | } 76 | 77 | public void setLoginTimeout(int i) throws SQLException { 78 | DriverManager.setLoginTimeout(i); 79 | } 80 | 81 | public int getLoginTimeout() throws SQLException { 82 | return DriverManager.getLoginTimeout(); 83 | } 84 | 85 | public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { 86 | throw new SQLFeatureNotSupportedException(); 87 | } 88 | 89 | public T unwrap(Class tClass) throws SQLException { 90 | throw new SQLFeatureNotSupportedException(); 91 | } 92 | 93 | public boolean isWrapperFor(Class aClass) throws SQLException { 94 | return false; 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/query/PhotonAggregateFilterQuery.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.query; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.sqlbuilders.SqlBuilderApplyOptionsService; 5 | import org.apache.commons.lang3.StringUtils; 6 | import com.github.molcikas.photon.blueprints.AggregateBlueprint; 7 | import com.github.molcikas.photon.exceptions.PhotonException; 8 | 9 | import java.sql.Connection; 10 | import java.util.List; 11 | 12 | public class PhotonAggregateFilterQuery 13 | { 14 | private final PhotonAggregateQuery photonAggregateQuery; 15 | private final PhotonQuery photonQuery; 16 | private final boolean isQueryIdsOnly; 17 | 18 | PhotonAggregateFilterQuery( 19 | AggregateBlueprint aggregateBlueprint, 20 | String selectSql, 21 | boolean isWhereClauseOnly, 22 | Connection connection, 23 | Photon photon, 24 | PhotonAggregateQuery photonAggregateQuery) 25 | { 26 | if(StringUtils.isBlank(selectSql)) 27 | { 28 | throw new PhotonException("Photon aggregate SELECT SQL cannot be blank."); 29 | } 30 | 31 | this.photonAggregateQuery = photonAggregateQuery; 32 | this.isQueryIdsOnly = !isWhereClauseOnly; 33 | 34 | if(isWhereClauseOnly) 35 | { 36 | selectSql = String.format(aggregateBlueprint.getAggregateRootEntityBlueprint().getTableBlueprint().getSelectWhereSql(), selectSql); 37 | selectSql = SqlBuilderApplyOptionsService.applyPhotonOptionsToSql(selectSql, photon.getOptions()); 38 | } 39 | 40 | this.photonQuery = new PhotonQuery(selectSql, false, connection, photon); 41 | } 42 | 43 | /** 44 | * Adds a parameter to the current query. 45 | * 46 | * @param parameter - The name of the parameter. Must match the name used in the SQL text for this query. 47 | * @param value - The parameter value 48 | * @return - The photon query (for chaining) 49 | */ 50 | public PhotonAggregateFilterQuery addParameter(String parameter, Object value) 51 | { 52 | photonQuery.addParameter(parameter, value); 53 | return this; 54 | } 55 | 56 | /** 57 | * Execute the query and use the first id in the result set to query for the aggregate. 58 | * 59 | * @return - The aggregate instance 60 | */ 61 | public T fetch() 62 | { 63 | if(isQueryIdsOnly) 64 | { 65 | return photonAggregateQuery.fetchByIdsQuery(photonQuery); 66 | } 67 | else 68 | { 69 | return photonAggregateQuery.fetchByQuery(photonQuery); 70 | } 71 | } 72 | 73 | /** 74 | * Execute the query and use the ids in the result set to query for aggregates. 75 | * 76 | * @return - The aggregate instances 77 | */ 78 | public List fetchList() 79 | { 80 | if(isQueryIdsOnly) 81 | { 82 | return photonAggregateQuery.fetchListByIdsQuery(photonQuery); 83 | } 84 | else 85 | { 86 | return photonAggregateQuery.fetchListByQuery(photonQuery); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/AdvancedLoadingAndSaving.md: -------------------------------------------------------------------------------- 1 | # Advanced Loading and Saving 2 | 3 | ## Change Tracking 4 | 5 | By default, photon tracks the state of each entity in each aggregate and only saves changes when `save()` is called. There is no concept of "flushing" changes. All queries (including inserts and updates) are always executed immediately. 6 | 7 | If a queried aggregate will not be updated during a transaction, you can disable change tracking to reduce memory usage and improve performance. 8 | 9 | ```java 10 | MyTable myTable = transaction 11 | .query(MyTable.class) 12 | .noTracking() 13 | .fetchById(2); 14 | ``` 15 | 16 | If you have a long-running transaction with aggregates falling out of scope and being garbage collected, you can clear the tracked state to reduce memory usage. 17 | 18 | ```java 19 | try(PhotonTransaction transaction = photon.beginTransaction()) 20 | { 21 | Recipe recipe = transaction 22 | .query(Recipe.class) 23 | .fetchById(UUID.fromString("3e038307-a9b6-11e6-ab83-0a0027000010")); 24 | 25 | // ... later, when the recipe entity falls out of scope ... 26 | 27 | transaction.untrack(recipe); 28 | } 29 | ``` 30 | 31 | Aggregates do not need to be tracked by Photon in order for them to save correctly. If an untracked aggregate is saved, the entire aggregate will be re-saved (as if the entire aggregate had changed). 32 | 33 | ## Lazy Loading 34 | 35 | Aggregates are loaded as whole units. Photon does not support "[lazy loading](http://www.mehdi-khalili.com/orm-anti-patterns-part-3-lazy-loading)" because an aggregate should not be used to control the loading of other aggregates. All entities in an aggregate are eager loaded. Therefore, it is important to keep your aggregates small. See [Effective Aggregate Design](https://vaughnvernon.co/?p=838) for more information on these design concepts. 36 | 37 | ## Partial Aggregate Loading and Saving 38 | 39 | While not recommended for most circumstances, Photon does support loading and saving partial aggregates. This can be useful if a simple update is needed and the overhead of loading and re-saving unmodified child entities would cause performance issues. 40 | 41 | ```java 42 | try(PhotonTransaction transaction = photon.beginTransaction()) 43 | { 44 | Recipe recipe = transaction 45 | .query(Recipe.class) 46 | .exclude("ingredients", "instructions") // Do not load the ingredient and instruction lists 47 | .fetchById(2); 48 | 49 | recipe.renameTo("Spaghetti and Meatballs"); 50 | 51 | // Do not save the ingredient and instruction lists since we did not load them, otherwise this 52 | // recipe would lose all of its ingredients and instructions. 53 | transaction.saveWithExcludedFields(recipe, "ingredients", "instructions"); 54 | 55 | transaction.commit(); 56 | } 57 | ``` 58 | 59 | It can also be useful for creating queries that only retrieve portions of an aggregate (although creating view models is preferred). 60 | 61 | ```java 62 | try(PhotonTransaction transaction = photon.beginTransaction()) 63 | { 64 | Recipe recipe = transaction 65 | .query(Recipe.class) 66 | .exclude("ingredients") // Do not load the ingredient list 67 | .fetchById(2); 68 | 69 | return recipe; 70 | ``` 71 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/photon/Recipe.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf.photon; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public class Recipe 7 | { 8 | private UUID recipeId; 9 | 10 | private String name; 11 | 12 | private String description; 13 | 14 | private int prepTime; 15 | 16 | private int cookTime; 17 | 18 | private int servings; 19 | 20 | private boolean isVegetarian; 21 | 22 | private boolean isVegan; 23 | 24 | private boolean isPublished; 25 | 26 | private String credit; 27 | 28 | private List ingredients; 29 | 30 | private List instructions; 31 | 32 | public UUID getRecipeId() 33 | { 34 | return recipeId; 35 | } 36 | 37 | public String getName() 38 | { 39 | return name; 40 | } 41 | 42 | public String getDescription() 43 | { 44 | return description; 45 | } 46 | 47 | public int getPrepTime() 48 | { 49 | return prepTime; 50 | } 51 | 52 | public int getCookTime() 53 | { 54 | return cookTime; 55 | } 56 | 57 | public int getServings() 58 | { 59 | return servings; 60 | } 61 | 62 | public boolean isVegetarian() 63 | { 64 | return isVegetarian; 65 | } 66 | 67 | public boolean isVegan() 68 | { 69 | return isVegan; 70 | } 71 | 72 | public boolean isPublished() 73 | { 74 | return isPublished; 75 | } 76 | 77 | public String getCredit() 78 | { 79 | return credit; 80 | } 81 | 82 | public List getIngredients() 83 | { 84 | return ingredients; 85 | } 86 | 87 | public List getInstructions() 88 | { 89 | return instructions; 90 | } 91 | 92 | // Anemic setters are strongly discouraged in aggregates, but we need these for testing. 93 | 94 | public void setInstructions(List instructions) 95 | { 96 | this.instructions = instructions; 97 | } 98 | 99 | public void setName(String name) 100 | { 101 | this.name = name; 102 | } 103 | 104 | public void setPrepTime(int prepTime) 105 | { 106 | this.prepTime = prepTime; 107 | } 108 | 109 | public Recipe() 110 | { 111 | } 112 | 113 | public Recipe(UUID recipeId, String name, String description, int prepTime, int cookTime, int servings, boolean isVegetarian, boolean isVegan, boolean isPublished, String credit, List ingredients, List instructions) 114 | { 115 | this.recipeId = recipeId; 116 | this.name = name; 117 | this.description = description; 118 | this.prepTime = prepTime; 119 | this.cookTime = cookTime; 120 | this.servings = servings; 121 | this.isVegetarian = isVegetarian; 122 | this.isVegan = isVegan; 123 | this.isPublished = isPublished; 124 | this.credit = credit; 125 | this.ingredients = ingredients; 126 | this.instructions = instructions; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/fieldtest/FieldTest.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.fieldtest; 2 | 3 | import java.time.Instant; 4 | import java.time.LocalDate; 5 | import java.time.LocalDateTime; 6 | import java.time.ZonedDateTime; 7 | import java.util.Date; 8 | 9 | public class FieldTest 10 | { 11 | private int id; 12 | private Date date; 13 | private ZonedDateTime zonedDateTime; 14 | private LocalDate localDate; 15 | private LocalDateTime localDateTime; 16 | private Instant instant; 17 | private TestEnum testEnumNumber; 18 | private TestEnum testEnumString; 19 | 20 | public int getId() 21 | { 22 | return id; 23 | } 24 | 25 | public void setId(int id) 26 | { 27 | this.id = id; 28 | } 29 | 30 | public Date getDate() 31 | { 32 | return date; 33 | } 34 | 35 | public void setDate(Date date) 36 | { 37 | this.date = date; 38 | } 39 | 40 | public ZonedDateTime getZonedDateTime() 41 | { 42 | return zonedDateTime; 43 | } 44 | 45 | public void setZonedDateTime(ZonedDateTime zonedDateTime) 46 | { 47 | this.zonedDateTime = zonedDateTime; 48 | } 49 | 50 | public LocalDate getLocalDate() 51 | { 52 | return localDate; 53 | } 54 | 55 | public void setLocalDate(LocalDate localDate) 56 | { 57 | this.localDate = localDate; 58 | } 59 | 60 | public TestEnum getTestEnumNumber() 61 | { 62 | return testEnumNumber; 63 | } 64 | 65 | public void setTestEnumNumber(TestEnum testEnumNumber) 66 | { 67 | this.testEnumNumber = testEnumNumber; 68 | } 69 | 70 | public TestEnum getTestEnumString() 71 | { 72 | return testEnumString; 73 | } 74 | 75 | public void setTestEnumString(TestEnum testEnumString) 76 | { 77 | this.testEnumString = testEnumString; 78 | } 79 | 80 | public LocalDateTime getLocalDateTime() 81 | { 82 | return localDateTime; 83 | } 84 | 85 | public void setLocalDateTime(LocalDateTime localDateTime) 86 | { 87 | this.localDateTime = localDateTime; 88 | } 89 | 90 | public Instant getInstant() 91 | { 92 | return instant; 93 | } 94 | 95 | public void setInstant(Instant instant) 96 | { 97 | this.instant = instant; 98 | } 99 | 100 | public FieldTest() 101 | { 102 | } 103 | 104 | public FieldTest(int id, 105 | Date date, 106 | ZonedDateTime zonedDateTime, 107 | LocalDate localDate, 108 | LocalDateTime localDateTime, 109 | Instant instant, 110 | TestEnum testEnumNumber, 111 | TestEnum testEnumString) 112 | { 113 | this.id = id; 114 | this.date = date; 115 | this.zonedDateTime = zonedDateTime; 116 | this.localDate = localDate; 117 | this.localDateTime = localDateTime; 118 | this.instant = instant; 119 | this.testEnumNumber = testEnumNumber; 120 | this.testEnumString = testEnumString; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/entity/FlattenedCollectionBlueprint.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.entity; 2 | 3 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 4 | import lombok.Getter; 5 | import org.apache.commons.lang3.StringUtils; 6 | import com.github.molcikas.photon.exceptions.PhotonException; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | public class FlattenedCollectionBlueprint 12 | { 13 | @Getter 14 | private final Class fieldClass; 15 | 16 | @Getter 17 | private final String tableName; 18 | 19 | @Getter 20 | private final String foreignKeyToParent; 21 | 22 | @Getter 23 | private final String foreignKeyToParentLowerCase; 24 | 25 | @Getter 26 | private final String columnName; 27 | 28 | @Getter 29 | private final String columnNameLowerCase; 30 | 31 | @Getter 32 | private final ColumnDataType columnDataType; 33 | 34 | @Getter 35 | private String selectSql; 36 | 37 | @Getter 38 | private String insertSql; 39 | 40 | @Getter 41 | private String deleteSql; 42 | 43 | @Getter 44 | private String deleteForeignKeysSql; 45 | 46 | public FlattenedCollectionBlueprint( 47 | Class fieldClass, 48 | String tableName, 49 | String foreignKeyToParent, 50 | String columnName, 51 | ColumnDataType columnDataType) 52 | { 53 | this.fieldClass = fieldClass; 54 | this.tableName = tableName; 55 | this.foreignKeyToParent = foreignKeyToParent; 56 | this.foreignKeyToParentLowerCase = foreignKeyToParent.toLowerCase(); 57 | this.columnName = columnName; 58 | this.columnNameLowerCase = columnName.toLowerCase(); 59 | this.columnDataType = columnDataType; 60 | } 61 | 62 | public List getSelectColumnNames() 63 | { 64 | return Arrays.asList(columnName, foreignKeyToParent); 65 | } 66 | 67 | public List getSelectColumnNamesLowerCase() 68 | { 69 | return Arrays.asList(columnNameLowerCase, foreignKeyToParentLowerCase); 70 | } 71 | 72 | public void setSelectSql(String selectSql) 73 | { 74 | if(StringUtils.isBlank(selectSql)) 75 | { 76 | throw new PhotonException("Select SQL cannot be blank."); 77 | } 78 | this.selectSql = selectSql; 79 | } 80 | 81 | public void setInsertSql(String insertSql) 82 | { 83 | if(StringUtils.isBlank(insertSql)) 84 | { 85 | throw new PhotonException("Insert SQL cannot be blank."); 86 | } 87 | this.insertSql = insertSql; 88 | } 89 | 90 | public void setDeleteSql(String deleteSql) 91 | { 92 | if(StringUtils.isBlank(deleteSql)) 93 | { 94 | throw new PhotonException("Delete SQL cannot be blank."); 95 | } 96 | this.deleteSql = deleteSql; 97 | } 98 | 99 | public void setDeleteForeignKeysSql(String deleteForeignKeysSql) 100 | { 101 | if(StringUtils.isBlank(deleteForeignKeysSql)) 102 | { 103 | throw new PhotonException("Delete Foreign Keys SQL cannot be blank."); 104 | } 105 | this.deleteForeignKeysSql = deleteForeignKeysSql; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /photon-core/src/test/resources/setup-shape-multi-table.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `drawing`; 2 | DROP TABLE IF EXISTS `shape`; 3 | DROP TABLE IF EXISTS `shapecolorhistory`; 4 | DROP TABLE IF EXISTS `circle`; 5 | DROP TABLE IF EXISTS `rectangle`; 6 | DROP TABLE IF EXISTS `cornercoordinates`; 7 | 8 | CREATE TABLE `drawing` ( 9 | `id` int(11) NOT NULL AUTO_INCREMENT, 10 | `name` varchar(255) NULL, 11 | PRIMARY KEY (`id`) 12 | ); 13 | 14 | CREATE TABLE `shape` ( 15 | `id` int(11) NOT NULL AUTO_INCREMENT, 16 | `type` varchar(255) NOT NULL, 17 | `color` varchar(255) NOT NULL, 18 | `drawingId` int(11) DEFAULT NULL, 19 | CONSTRAINT `shape_drawing` FOREIGN KEY (`drawingId`) REFERENCES `drawing` (`id`), 20 | PRIMARY KEY (`id`) 21 | ); 22 | 23 | CREATE TABLE `shapecolorhistory` ( 24 | `id` int(11) NOT NULL AUTO_INCREMENT, 25 | `shapeId` int(11) NOT NULL, 26 | `dateChanged` datetime NOT NULL, 27 | `colorName` varchar(255) NOT NULL, 28 | PRIMARY KEY (`id`), 29 | CONSTRAINT `shapecolorhistory_shape` FOREIGN KEY (`shapeId`) REFERENCES `shape` (`id`) 30 | ); 31 | 32 | CREATE TABLE `circle` ( 33 | `id` int(11) NOT NULL AUTO_INCREMENT, 34 | `radius` int(11) NULL, 35 | PRIMARY KEY (`id`), 36 | CONSTRAINT `circle_shape` FOREIGN KEY (`id`) REFERENCES `shape` (`id`) 37 | ); 38 | 39 | CREATE TABLE `rectangle` ( 40 | `id` int(11) NOT NULL AUTO_INCREMENT, 41 | `width` int(11) NULL, 42 | `height` int(11) NULL, 43 | PRIMARY KEY (`id`), 44 | CONSTRAINT `rectangle_shape` FOREIGN KEY (`id`) REFERENCES `shape` (`id`) 45 | ); 46 | 47 | CREATE TABLE `cornercoordinates` ( 48 | `id` int(11) NOT NULL AUTO_INCREMENT, 49 | `shapeId` int(11) NOT NULL, 50 | `x` int(11) NULL, 51 | `y` int(11) NULL, 52 | PRIMARY KEY (`id`), 53 | CONSTRAINT `cornerCoordinates_shape` FOREIGN KEY (`shapeId`) REFERENCES `shape` (`id`) 54 | ); 55 | 56 | insert into `drawing` (`id`) values (1); 57 | 58 | insert into `shape` (`id`, `type`, `color`, `drawingId`) values (1, 'circle', 'red', 1); 59 | insert into `circle` (`id`, `radius`) values (1, 3); 60 | 61 | insert into `shape` (`id`, `type`, `color`, `drawingId`) values (2, 'rectangle', 'blue', 1); 62 | insert into `rectangle` (`id`, `width`, `height`) values (2, 7, 8); 63 | 64 | insert into `shape` (`id`, `type`, `color`, `drawingId`) values (3, 'circle', 'orange', 1); 65 | insert into `circle` (`id`, `radius`) values (3, 4); 66 | insert into `shapecolorhistory` (`id`, `shapeId`, `dateChanged`, `colorName`) values (1, 3, PARSEDATETIME('2017-03-19 09-28-17', 'yyyy-MM-dd HH-mm-ss'), 'creamsicle'); 67 | insert into `shapecolorhistory` (`id`, `shapeId`, `dateChanged`, `colorName`) values (2, 3, PARSEDATETIME('2017-04-20 10-29-18', 'yyyy-MM-dd HH-mm-ss'), 'yellow'); 68 | 69 | insert into `shape` (`id`, `type`, `color`, `drawingId`) values (4, 'rectangle', 'white', 1); 70 | insert into `rectangle` (`id`, `width`, `height`) values (4, 11, 9); 71 | insert into `shapecolorhistory` (`id`, `shapeId`, `dateChanged`, `colorName`) values (3, 4, PARSEDATETIME('2017-04-11 11-28-17', 'yyyy-MM-dd HH-mm-ss'), 'beige'); 72 | insert into `shapecolorhistory` (`id`, `shapeId`, `dateChanged`, `colorName`) values (4, 4, PARSEDATETIME('2017-04-22 12-29-18', 'yyyy-MM-dd HH-mm-ss'), 'gray'); 73 | insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (1, 4, 0, 0); 74 | insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (2, 4, 7, 0); 75 | insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (3, 4, 0, 8); 76 | insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (4, 4, 7, 8); -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/shape/ShapeDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.shape; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class ShapeDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | return setupDatabase(photon); 13 | } 14 | 15 | public static Photon setupDatabase(Photon photon) 16 | { 17 | try(PhotonTransaction transaction = photon.beginTransaction()) 18 | { 19 | transaction.query("DROP TABLE IF EXISTS `drawing`").executeUpdate(); 20 | transaction.query("CREATE TABLE `drawing` (\n" + 21 | "`id` int(11) NOT NULL AUTO_INCREMENT,\n" + 22 | "`name` varchar(255) NULL,\n" + 23 | "PRIMARY KEY (`id`)\n" + 24 | ")").executeUpdate(); 25 | 26 | transaction.query("insert into `drawing` (`id`) values (1)").executeUpdate(); 27 | 28 | transaction.query("DROP TABLE IF EXISTS `shape`").executeUpdate(); 29 | transaction.query("CREATE TABLE `shape` (\n" + 30 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 31 | " `type` varchar(255) NOT NULL,\n" + 32 | " `color` varchar(255) NOT NULL,\n" + 33 | " `radius` int(11) NULL,\n" + 34 | " `width` int(11) NULL,\n" + 35 | " `height` int(11) NULL,\n" + 36 | " `drawingId` int(11) DEFAULT NULL,\n" + 37 | " CONSTRAINT `shape_drawing` FOREIGN KEY (`drawingId`) REFERENCES `drawing` (`id`),\n" + 38 | " PRIMARY KEY (`id`)\n" + 39 | ")").executeUpdate(); 40 | 41 | transaction.query("insert into `shape` (`id`, `type`, `color`, `radius`, `width`, `height`) values (1, 'circle', 'red', 3, NULL, NULL)").executeUpdate(); 42 | transaction.query("insert into `shape` (`id`, `type`, `color`, `radius`, `width`, `height`) values (2, 'rectangle', 'blue', NULL, 7, 8)").executeUpdate(); 43 | 44 | transaction.query("DROP TABLE IF EXISTS `cornercoordinates`").executeUpdate(); 45 | transaction.query("CREATE TABLE `cornercoordinates` (\n" + 46 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 47 | " `shapeId` int(11) NOT NULL,\n" + 48 | " `x` int(11) NULL,\n" + 49 | " `y` int(11) NULL,\n" + 50 | " PRIMARY KEY (`id`),\n" + 51 | " CONSTRAINT `CornerCoordinates_Shape` FOREIGN KEY (`shapeId`) REFERENCES `shape` (`id`)\n" + 52 | ")").executeUpdate(); 53 | 54 | transaction.query("insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (1, 2, 0, 0)").executeUpdate(); 55 | transaction.query("insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (2, 2, 7, 0)").executeUpdate(); 56 | transaction.query("insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (3, 2, 0, 8)").executeUpdate(); 57 | transaction.query("insert into `cornercoordinates` (`id`, `shapeId`, `x`, `y`) values (4, 2, 7, 8)").executeUpdate(); 58 | 59 | transaction.commit(); 60 | } 61 | 62 | return photon; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/table/ColumnBlueprint.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints.table; 2 | 3 | import com.github.molcikas.photon.blueprints.entity.FieldBlueprint; 4 | import lombok.EqualsAndHashCode; 5 | import org.apache.commons.lang3.StringUtils; 6 | import com.github.molcikas.photon.converters.Converter; 7 | import com.github.molcikas.photon.exceptions.PhotonException; 8 | 9 | @EqualsAndHashCode 10 | public class ColumnBlueprint 11 | { 12 | private final String columnNameQualified; 13 | private final String columnName; 14 | private final ColumnDataType columnDataType; 15 | private final boolean isPrimaryKeyColumn; 16 | private final boolean isAutoIncrementColumn; 17 | private final boolean isForeignKeyToParentColumn; 18 | private final Converter customSerializer; 19 | 20 | // Reference to the entity field that this database column is mapped to. This can (but does not have to) 21 | // be null if this column is an unmapped primary key or a foreign key to the parent. 22 | private final FieldBlueprint mappedFieldBlueprint; 23 | 24 | private int columnIndex; 25 | 26 | public String getColumnNameQualified() 27 | { 28 | return columnNameQualified; 29 | } 30 | 31 | public String getColumnName() 32 | { 33 | return columnName; 34 | } 35 | 36 | public ColumnDataType getColumnDataType() 37 | { 38 | return columnDataType; 39 | } 40 | 41 | public boolean isPrimaryKeyColumn() 42 | { 43 | return isPrimaryKeyColumn; 44 | } 45 | 46 | public boolean isAutoIncrementColumn() 47 | { 48 | return isAutoIncrementColumn; 49 | } 50 | 51 | public boolean isForeignKeyToParentColumn() 52 | { 53 | return isForeignKeyToParentColumn; 54 | } 55 | 56 | public Converter getCustomSerializer() 57 | { 58 | return customSerializer; 59 | } 60 | 61 | public FieldBlueprint getMappedFieldBlueprint() 62 | { 63 | return mappedFieldBlueprint; 64 | } 65 | 66 | public int getColumnIndex() 67 | { 68 | return columnIndex; 69 | } 70 | 71 | public ColumnBlueprint( 72 | String tableName, 73 | String columnName, 74 | ColumnDataType columnDataType, 75 | boolean isPrimaryKeyColumn, 76 | boolean isAutoIncrementColumn, 77 | boolean isForeignKeyToParentColumn, 78 | Converter customSerializer, 79 | FieldBlueprint mappedFieldBlueprint, 80 | int columnIndex) 81 | { 82 | if(StringUtils.isBlank(tableName)) 83 | { 84 | throw new PhotonException("Table name cannot be blank."); 85 | } 86 | if(StringUtils.isBlank(columnName)) 87 | { 88 | throw new PhotonException("Column name cannot be blank."); 89 | } 90 | if(isAutoIncrementColumn && !isPrimaryKeyColumn) 91 | { 92 | throw new PhotonException("The column '%s' cannot be auto-increment because it is not the primary key.", columnName); 93 | } 94 | this.columnNameQualified = tableName + "_" + columnName; 95 | this.columnName = columnName; 96 | this.columnDataType = columnDataType; 97 | this.isPrimaryKeyColumn = isPrimaryKeyColumn; 98 | this.isAutoIncrementColumn = isAutoIncrementColumn; 99 | this.isForeignKeyToParentColumn = isForeignKeyToParentColumn; 100 | this.customSerializer = customSerializer; 101 | this.mappedFieldBlueprint = mappedFieldBlueprint; 102 | this.columnIndex = columnIndex; 103 | } 104 | 105 | void moveColumnToIndex(int newColumnIndex) 106 | { 107 | this.columnIndex = newColumnIndex; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /photon-perf-test/src/test/java/com/github/molcikas/photon/perf/RecipeDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.perf; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import com.github.molcikas.photon.Photon; 6 | import com.github.molcikas.photon.PhotonTransaction; 7 | 8 | import javax.sql.DataSource; 9 | 10 | public class RecipeDbSetup 11 | { 12 | private static final String h2Url = "jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_DELAY=-1"; 13 | private static final String h2User = "sa"; 14 | private static final String h2Password = ""; 15 | 16 | public static DataSource createDataSource() 17 | { 18 | HikariConfig hikariConfig = new HikariConfig(); 19 | hikariConfig.setJdbcUrl(h2Url); 20 | hikariConfig.setUsername(h2User); 21 | hikariConfig.setPassword(h2Password); 22 | hikariConfig.addDataSourceProperty("cachePrepStmts", "true"); 23 | hikariConfig.addDataSourceProperty("prepStmtCacheSize", "250"); 24 | hikariConfig.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); 25 | 26 | return new HikariDataSource(hikariConfig); 27 | } 28 | 29 | public static void setupDatabase() 30 | { 31 | Photon photon = new Photon(h2Url, h2User, h2Password); 32 | 33 | try(PhotonTransaction transaction = photon.beginTransaction()) 34 | { 35 | transaction.query("DROP TABLE IF EXISTS recipe").executeUpdate(); 36 | transaction.query("CREATE TABLE recipe (\n" + 37 | " recipeId binary(16) NOT NULL,\n" + 38 | " name varchar(50) NOT NULL,\n" + 39 | " description text NOT NULL,\n" + 40 | " prepTime int(10) unsigned NOT NULL,\n" + 41 | " cookTime int(10) unsigned NOT NULL,\n" + 42 | " servings int(10) unsigned NOT NULL,\n" + 43 | " isVegetarian tinyint(1) NOT NULL,\n" + 44 | " isVegan tinyint(1) NOT NULL,\n" + 45 | " isPublished tinyint(1) NOT NULL,\n" + 46 | " credit varchar(512) DEFAULT NULL,\n" + 47 | " PRIMARY KEY (recipeId)\n" + 48 | ")").executeUpdate(); 49 | 50 | transaction.query("DROP TABLE IF EXISTS recipeingredient").executeUpdate(); 51 | transaction.query("CREATE TABLE recipeingredient (\n" + 52 | " recipeIngredientId binary(16) NOT NULL,\n" + 53 | " recipeId binary(16) NOT NULL,\n" + 54 | " isRequired tinyint(1) NOT NULL,\n" + 55 | " quantity varchar(64) DEFAULT NULL,\n" + 56 | " quantityUnit varchar(64) DEFAULT NULL,\n" + 57 | " quantityDetail varchar(64) DEFAULT NULL,\n" + 58 | " name varchar(128) NOT NULL,\n" + 59 | " preparation varchar(128) DEFAULT NULL,\n" + 60 | " orderBy int(11) NOT NULL,\n" + 61 | " PRIMARY KEY (recipeIngredientId),\n" + 62 | " CONSTRAINT RecipeIngredient_Recipe FOREIGN KEY (recipeId) REFERENCES recipe (recipeId) ON DELETE NO ACTION ON UPDATE CASCADE\n" + 63 | ")").executeUpdate(); 64 | 65 | transaction.query("DROP TABLE IF EXISTS recipeinstruction").executeUpdate(); 66 | transaction.query("CREATE TABLE recipeinstruction (\n" + 67 | " recipeInstructionId binary(16) NOT NULL,\n" + 68 | " recipeId binary(16) NOT NULL,\n" + 69 | " stepNumber int(10) unsigned NOT NULL,\n" + 70 | " description text NOT NULL,\n" + 71 | " PRIMARY KEY (recipeInstructionId),\n" + 72 | " CONSTRAINT RecipeInstruction_Recipe FOREIGN KEY (recipeId) REFERENCES recipe (recipeId) ON DELETE NO ACTION ON UPDATE CASCADE\n" + 73 | ")").executeUpdate(); 74 | 75 | transaction.commit(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/shape/ShapeChildEntityTests.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.shape; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 6 | import com.github.molcikas.photon.tests.unit.entities.shape.Circle; 7 | import com.github.molcikas.photon.tests.unit.entities.shape.CornerCoordinates; 8 | import com.github.molcikas.photon.tests.unit.entities.shape.Rectangle; 9 | import com.github.molcikas.photon.tests.unit.entities.shape.Shape; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | import static org.junit.Assert.*; 14 | 15 | public class ShapeChildEntityTests 16 | { 17 | private Photon photon; 18 | 19 | @Before 20 | public void setupDatabase() 21 | { 22 | photon = ShapeDbSetup.setupDatabase(); 23 | } 24 | 25 | @Test 26 | public void withMappedClass_selectExistingRows_createsAggregate() 27 | { 28 | registerAggregate(); 29 | 30 | try (PhotonTransaction transaction = photon.beginTransaction()) 31 | { 32 | Rectangle rectangle = transaction 33 | .query(Rectangle.class) 34 | .fetchById(2); 35 | 36 | assertNotNull(rectangle); 37 | assertEquals(Integer.valueOf(2), rectangle.getId()); 38 | assertEquals("blue", rectangle.getColor()); 39 | 40 | assertEquals(4, rectangle.getCorners().size()); 41 | assertEquals(Integer.valueOf(0), rectangle.getCorners().get(0).getX()); 42 | assertEquals(Integer.valueOf(7), rectangle.getCorners().get(1).getX()); 43 | assertEquals(Integer.valueOf(8), rectangle.getCorners().get(3).getY()); 44 | } 45 | } 46 | 47 | @Test 48 | public void withMappedClass_updateExistingRows_updatesAggregate() 49 | { 50 | registerAggregate(); 51 | 52 | try (PhotonTransaction transaction = photon.beginTransaction()) 53 | { 54 | Rectangle rectangle = transaction 55 | .query(Rectangle.class) 56 | .fetchById(2); 57 | 58 | rectangle.getCorners().remove(1); 59 | rectangle.getCorners().get(2).setX(3); 60 | transaction.save(rectangle); 61 | transaction.commit(); 62 | } 63 | 64 | try (PhotonTransaction transaction = photon.beginTransaction()) 65 | { 66 | Rectangle rectangle = transaction 67 | .query(Rectangle.class) 68 | .fetchById(2); 69 | 70 | assertEquals(3, rectangle.getCorners().size()); 71 | assertEquals(Integer.valueOf(0), rectangle.getCorners().get(0).getX()); 72 | assertEquals(Integer.valueOf(8), rectangle.getCorners().get(1).getY()); 73 | assertEquals(Integer.valueOf(3), rectangle.getCorners().get(2).getX()); 74 | } 75 | } 76 | 77 | private void registerAggregate() 78 | { 79 | photon.registerAggregate(Shape.class) 80 | .withMappedClass(Circle.class) 81 | .withMappedClass(Rectangle.class) 82 | .withClassDiscriminator(valuesMap -> 83 | { 84 | String type = (String) valuesMap.get("Shape_type"); 85 | switch (type) 86 | { 87 | case "circle": 88 | return Circle.class; 89 | case "rectangle": 90 | return Rectangle.class; 91 | default: 92 | return null; 93 | } 94 | }) 95 | .withChild("corners", CornerCoordinates.class) 96 | .withForeignKeyToParent("shapeId", ColumnDataType.INTEGER) 97 | .addAsChild() 98 | .register(); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/shape/ShapeQueryTests.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.shape; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.entities.shape.Circle; 6 | import com.github.molcikas.photon.tests.unit.entities.shape.Rectangle; 7 | import com.github.molcikas.photon.tests.unit.entities.shape.Shape; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.util.List; 12 | 13 | import static junit.framework.TestCase.assertNotNull; 14 | import static org.junit.Assert.assertEquals; 15 | 16 | public class ShapeQueryTests 17 | { 18 | private Photon photon; 19 | 20 | @Before 21 | public void setupDatabase() 22 | { 23 | photon = ShapeDbSetup.setupDatabase(); 24 | } 25 | 26 | @Test 27 | public void withMappedClass_selectExistingRow_fetchesObject() 28 | { 29 | registerAggregate(); 30 | 31 | try (PhotonTransaction transaction = photon.beginTransaction()) 32 | { 33 | Circle circle = transaction 34 | .query("SELECT * FROM shape WHERE id = 1") 35 | .withMappedClass(Shape.class) 36 | .fetch(Circle.class); 37 | 38 | assertNotNull(circle); 39 | assertEquals(Integer.valueOf(1), circle.getId()); 40 | assertEquals("red", circle.getColor()); 41 | assertEquals(3, circle.getRadius()); 42 | } 43 | } 44 | 45 | @Test 46 | public void withMappedClass_selectExistingRows_fetchesList() 47 | { 48 | registerAggregate(); 49 | 50 | try (PhotonTransaction transaction = photon.beginTransaction()) 51 | { 52 | List shapes = transaction 53 | .query("SELECT * FROM shape WHERE id IN (1, 2)") 54 | .withMappedClass(Circle.class) 55 | .withMappedClass(Rectangle.class) 56 | .withClassDiscriminator(valuesMap -> 57 | { 58 | String type = (String) valuesMap.get("type"); 59 | switch (type) 60 | { 61 | case "circle": 62 | return Circle.class; 63 | case "rectangle": 64 | return Rectangle.class; 65 | default: 66 | return null; 67 | } 68 | }) 69 | .fetchList(Shape.class); 70 | 71 | assertEquals(2, shapes.size()); 72 | 73 | Circle circle = (Circle) shapes.get(0); 74 | assertEquals(Integer.valueOf(1), circle.getId()); 75 | assertEquals("red", circle.getColor()); 76 | assertEquals(3, circle.getRadius()); 77 | 78 | Rectangle rectangle = (Rectangle) shapes.get(1); 79 | assertEquals(Integer.valueOf(2), rectangle.getId()); 80 | assertEquals("blue", rectangle.getColor()); 81 | assertEquals(7, rectangle.getWidth()); 82 | assertEquals(8, rectangle.getHeight()); 83 | } 84 | } 85 | 86 | private void registerAggregate() 87 | { 88 | photon.registerAggregate(Shape.class) 89 | .withMappedClass(Circle.class) 90 | .withMappedClass(Rectangle.class) 91 | .withClassDiscriminator(valuesMap -> 92 | { 93 | String type = (String) valuesMap.get("Shape_type"); 94 | switch (type) 95 | { 96 | case "circle": 97 | return Circle.class; 98 | case "rectangle": 99 | return Rectangle.class; 100 | default: 101 | return null; 102 | } 103 | }) 104 | .register(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/twoaggregates/TwoAggregatesDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.twoaggregates; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class TwoAggregatesDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | 13 | try(PhotonTransaction transaction = photon.beginTransaction()) 14 | { 15 | transaction.query("DROP TABLE IF EXISTS `aggregateone`").executeUpdate(); 16 | transaction.query("CREATE TABLE `aggregateone` (\n" + 17 | " `aggregateOneId` binary(16) NOT NULL,\n" + 18 | " `myValue` varchar(255) DEFAULT 'oops',\n" + 19 | " PRIMARY KEY (`aggregateOneId`)\n" + 20 | ")").executeUpdate(); 21 | transaction.query("insert into `aggregateone` (`aggregateOneId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000010', 'agg1val0')").executeUpdate(); 22 | transaction.query("insert into `aggregateone` (`aggregateOneId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000011', 'agg1val1')").executeUpdate(); 23 | transaction.query("insert into `aggregateone` (`aggregateOneId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000012', 'agg1val2')").executeUpdate(); 24 | 25 | transaction.query("DROP TABLE IF EXISTS `aggregatetwo`").executeUpdate(); 26 | transaction.query("CREATE TABLE `aggregatetwo` (\n" + 27 | " `aggregateOneId` binary(16) NOT NULL,\n" + 28 | " `aggregateTwoId` binary(16) NOT NULL,\n" + 29 | " `myValue` varchar(255) DEFAULT 'oops',\n" + 30 | " PRIMARY KEY (`aggregateTwoId`)\n" + 31 | ")").executeUpdate(); 32 | transaction.query("insert into `aggregatetwo` (`aggregateTwoId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000020', 'agg2val0')").executeUpdate(); 33 | transaction.query("insert into `aggregatetwo` (`aggregateTwoId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000021', 'agg2val1')").executeUpdate(); 34 | transaction.query("insert into `aggregatetwo` (`aggregateTwoId`, `myValue`) values (X'3DFFC3B3A9B611E6AB830A0027000022', 'agg2val2')").executeUpdate(); 35 | 36 | transaction.query("DROP TABLE IF EXISTS `aggregatemapping`").executeUpdate(); 37 | transaction.query("CREATE TABLE `aggregatemapping` (\n" + 38 | " `aggregateOneId` binary(16) NOT NULL,\n" + 39 | " `aggregateTwoId` binary(16) NOT NULL,\n" + 40 | " PRIMARY KEY (`aggregateOneId`, `aggregateTwoId`),\n" + 41 | " CONSTRAINT `AggregateMapping_AggregateOne` FOREIGN KEY (`aggregateOneId`) REFERENCES `aggregateone` (`aggregateOneId`),\n" + 42 | " CONSTRAINT `AggregateMapping_AggregateTwo` FOREIGN KEY (`aggregateTwoId`) REFERENCES `aggregatetwo` (`aggregateTwoId`)\n" + 43 | ")").executeUpdate(); 44 | transaction.query("insert into `aggregatemapping` (`aggregateOneId`, `aggregateTwoId`) values (X'3DFFC3B3A9B611E6AB830A0027000011', X'3DFFC3B3A9B611E6AB830A0027000021')").executeUpdate(); 45 | transaction.query("insert into `aggregatemapping` (`aggregateOneId`, `aggregateTwoId`) values (X'3DFFC3B3A9B611E6AB830A0027000012', X'3DFFC3B3A9B611E6AB830A0027000020')").executeUpdate(); 46 | transaction.query("insert into `aggregatemapping` (`aggregateOneId`, `aggregateTwoId`) values (X'3DFFC3B3A9B611E6AB830A0027000012', X'3DFFC3B3A9B611E6AB830A0027000021')").executeUpdate(); 47 | transaction.query("insert into `aggregatemapping` (`aggregateOneId`, `aggregateTwoId`) values (X'3DFFC3B3A9B611E6AB830A0027000012', X'3DFFC3B3A9B611E6AB830A0027000022')").executeUpdate(); 48 | 49 | transaction.commit(); 50 | } 51 | 52 | return photon; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/ValueObjects.md: -------------------------------------------------------------------------------- 1 | # Working with Value Objects 2 | 3 | ORMs typically very little support for . They expect you to only use primitive values likes strings and integers in your entities. Photon provides several mechanisms to make it easier for you to use value objects. 4 | 5 | If you have a value object in an entity, you can customize how that value object is hydrated from the database or serialized into the database. 6 | 7 | ```java 8 | photon.registerAggregate(RecipeIngredient.class) 9 | // The quantity is a VARCHAR in the database but is converted to a Fraction value object when hydrated. 10 | .withDatabaseColumnSerializer("quantity", fraction -> fraction.getNumerator() + "/" + fraction.getDenominator()) 11 | .withFieldHydrater("quantity", fractionString -> Fraction.getFraction(fractionString)) 12 | .register(); 13 | ``` 14 | 15 | If you have a common value object that is used in multiple entities in your application, and the value object maps to a single database column value using its `toString()` method, you can register a global custom converter for hydrating it. 16 | 17 | ```java 18 | Photon.registerConverter(Fraction.class, fraction -> Fraction.getFraction(fraction)); 19 | ``` 20 | 21 | If you have a value object that does not neatly map between an entity field and a database column, you can create a custom field value mapper. 22 | 23 | ```java 24 | photon.registerAggregate(Product.class) 25 | .withDatabaseColumn("quantity", ColumnDataType.INTEGER, new EntityFieldValueMapping() 26 | { 27 | @Override 28 | public String getFieldValue(MyTable entityInstance) 29 | { 30 | return entityInstance.getQuantityInfo().getQuantity(); 31 | } 32 | 33 | @Override 34 | public Map setFieldValue(MyTable entityInstance, Integer value) 35 | { 36 | entityInstance.getQuantityInfo().updateQuantity(value); 37 | return null; 38 | } 39 | } 40 | ) 41 | .register(); 42 | ``` 43 | 44 | If you have a value object that maps to multiple database columns, you can create a custom mapper for the value object. 45 | 46 | ```java 47 | photon.registerAggregate(Product.class) 48 | .withDatabaseColumns( 49 | Arrays.asList( 50 | new DatabaseColumnDefinition("numerator", ColumnDataType.INTEGER), 51 | new DatabaseColumnDefinition("denominator", ColumnDataType.INTEGER) 52 | ), 53 | new CompoundEntityFieldValueMapping() 54 | { 55 | @Override 56 | public Map getDatabaseValues(Product entityInstance) 57 | { 58 | Map values = new HashMap<>(); 59 | values.put("numerator", entityInstance.getQuantity().getNumerator()); 60 | values.put("denominator", entityInstance.getQuantity().getDenominator()); 61 | return values; 62 | } 63 | 64 | @Override 65 | public Map setFieldValues(Product entityInstance, Map values) 66 | { 67 | Map valuesToSet = new HashMap<>(); 68 | valuesToSet.put( 69 | "quantity", 70 | Fraction.getFraction((int) values.get("numerator"), (int) values.get("denominator")) 71 | ); 72 | return valuesToSet; 73 | } 74 | } 75 | ) 76 | .register(); 77 | ``` 78 | 79 | If you have a value object that is a list of items, you can add the list as a child of the entity and optionally omit the id (since value objects are not supposed to have an id) and the foreign key to the parent. However, if you omit the id, Photon won't be able to link each object in the list to its database row, so the entire set of rows will be deleted and re-inserted whenever the aggregate is saved. 80 | 81 | ```java 82 | photon.registerAggregate(Recipe.class) 83 | .withChild("ingredients", RecipeIngredient.class) 84 | // The id and foreign key to parent must be specified here, but do not need to be in 85 | // the RecipeIngredient class. 86 | .withId("recipeIngredientId") 87 | .withForeignKeyToParent("recipeId") 88 | .addAsChild() 89 | .register(); 90 | ``` -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/options/PhotonOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.options; 2 | 3 | import com.github.molcikas.photon.blueprints.table.ColumnDataType; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | /** 8 | * The options for photon. 9 | */ 10 | @Getter 11 | public class PhotonOptions 12 | { 13 | public static final ColumnDataType DEFAULT_UUID_DATA_TYPE = ColumnDataType.BINARY; 14 | 15 | private final String delimitIdentifierStart; 16 | private final String delimitIdentifierEnd; 17 | private final DefaultTableName defaultTableName; 18 | private final boolean enableJdbcGetGeneratedKeys; 19 | private final ColumnDataType defaultUuidDataType; 20 | 21 | /** 22 | * Constructor. Defaults the UUID data type to PhotonOptions.DEFAULT_UUID_DATA_TYPE. 23 | * 24 | * @param delimitIdentifierStart - The delimiter identifier start (e.g. "[" for SQL Server or "`" for MySQL) 25 | * @param delimitIdentifierEnd - The delimiter identifier end (e.g. "]" for SQL Server or "`" for MySQL) 26 | * @param defaultTableName - The strategy for determining the default table name for an entity 27 | * @param enableJdbcGetGeneratedKeys - Whether to use Statement.RETURN_GENERATED_KEYS when creating prepared 28 | * statements. Set this to false for Oracle databases. 29 | * @param defaultUuidDataType - Default java.sql.Types value for UUID fields. Set this to null for Postgres 30 | * databases. 31 | */ 32 | @Builder 33 | public PhotonOptions( 34 | String delimitIdentifierStart, 35 | String delimitIdentifierEnd, 36 | DefaultTableName defaultTableName, 37 | Boolean enableJdbcGetGeneratedKeys, 38 | ColumnDataType defaultUuidDataType) 39 | { 40 | this.delimitIdentifierStart = delimitIdentifierStart != null ? delimitIdentifierStart : ""; 41 | this.delimitIdentifierEnd = delimitIdentifierEnd != null ? delimitIdentifierEnd : ""; 42 | this.defaultTableName = defaultTableName != null ? defaultTableName : DefaultTableName.ClassName; 43 | this.enableJdbcGetGeneratedKeys = enableJdbcGetGeneratedKeys != null ? enableJdbcGetGeneratedKeys : true; 44 | this.defaultUuidDataType = defaultUuidDataType; 45 | } 46 | 47 | /** 48 | * Creates a PhotonOptions object with the default options. 49 | * 50 | * @return - the photon options 51 | */ 52 | public static PhotonOptions defaultOptions() 53 | { 54 | return new PhotonOptions(null, null, null, null, DEFAULT_UUID_DATA_TYPE); 55 | } 56 | 57 | /** 58 | * Recommended options for MySQL databases. 59 | * 60 | * @return - the photon options 61 | */ 62 | public static PhotonOptionsBuilder mysqlOptions() 63 | { 64 | return PhotonOptions 65 | .builder() 66 | .delimitIdentifierStart("`") 67 | .delimitIdentifierEnd("`") 68 | .defaultUuidDataType(ColumnDataType.BINARY); 69 | } 70 | 71 | /** 72 | * Recommended options for Oracle databases. 73 | * 74 | * @return - the photon options 75 | */ 76 | public static PhotonOptionsBuilder oracleOptions() 77 | { 78 | return PhotonOptions 79 | .builder() 80 | .enableJdbcGetGeneratedKeys(false) 81 | .defaultUuidDataType(ColumnDataType.BINARY); 82 | } 83 | 84 | /** 85 | * Recommended options for Postgres databases. 86 | * 87 | * @return - the photon options 88 | */ 89 | public static PhotonOptionsBuilder postgresOptions() 90 | { 91 | return PhotonOptions 92 | .builder() 93 | .delimitIdentifierStart("\"") 94 | .delimitIdentifierEnd("\"") 95 | .defaultUuidDataType(null); 96 | } 97 | 98 | /** 99 | * Recommended options for SQL Server databases. 100 | * 101 | * @return - the photon options 102 | */ 103 | public static PhotonOptionsBuilder sqlServerOptions() 104 | { 105 | return PhotonOptions 106 | .builder() 107 | .delimitIdentifierStart("[") 108 | .delimitIdentifierEnd("]") 109 | .defaultUuidDataType(ColumnDataType.BINARY); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/entities/recipe/Recipe.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.entities.recipe; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import java.util.UUID; 6 | 7 | public class Recipe 8 | { 9 | private UUID recipeId; 10 | 11 | private String name; 12 | 13 | private String description; 14 | 15 | private int prepTime; 16 | 17 | private int cookTime; 18 | 19 | private int servings; 20 | 21 | private boolean isVegetarian; 22 | 23 | private boolean isVegan; 24 | 25 | private boolean isPublished; 26 | 27 | private String credit; 28 | 29 | private List ingredients; 30 | 31 | private List instructions; 32 | 33 | public UUID getRecipeId() 34 | { 35 | return recipeId; 36 | } 37 | 38 | public String getName() 39 | { 40 | return name; 41 | } 42 | 43 | public String getDescription() 44 | { 45 | return description; 46 | } 47 | 48 | public int getPrepTime() 49 | { 50 | return prepTime; 51 | } 52 | 53 | public int getCookTime() 54 | { 55 | return cookTime; 56 | } 57 | 58 | public int getServings() 59 | { 60 | return servings; 61 | } 62 | 63 | public boolean isVegetarian() 64 | { 65 | return isVegetarian; 66 | } 67 | 68 | public boolean isVegan() 69 | { 70 | return isVegan; 71 | } 72 | 73 | public boolean isPublished() 74 | { 75 | return isPublished; 76 | } 77 | 78 | public String getCredit() 79 | { 80 | return credit; 81 | } 82 | 83 | public List getIngredients() 84 | { 85 | return ingredients; 86 | } 87 | 88 | public List getInstructions() 89 | { 90 | return instructions; 91 | } 92 | 93 | // Anemic setters are strongly discouraged in aggregates, but we need these for testing. 94 | 95 | public void setInstructions(List instructions) 96 | { 97 | this.instructions = instructions; 98 | } 99 | 100 | public void setName(String name) 101 | { 102 | this.name = name; 103 | } 104 | 105 | public void setPrepTime(int prepTime) 106 | { 107 | this.prepTime = prepTime; 108 | } 109 | 110 | private Recipe() 111 | { 112 | } 113 | 114 | public Recipe(UUID recipeId, String name, String description, int prepTime, int cookTime, int servings, boolean isVegetarian, boolean isVegan, boolean isPublished, String credit, List ingredients, List instructions) 115 | { 116 | this.recipeId = recipeId; 117 | this.name = name; 118 | this.description = description; 119 | this.prepTime = prepTime; 120 | this.cookTime = cookTime; 121 | this.servings = servings; 122 | this.isVegetarian = isVegetarian; 123 | this.isVegan = isVegan; 124 | this.isPublished = isPublished; 125 | this.credit = credit; 126 | this.ingredients = ingredients; 127 | this.instructions = instructions; 128 | } 129 | 130 | @Override 131 | public boolean equals(Object o) 132 | { 133 | if (this == o) return true; 134 | if (o == null || getClass() != o.getClass()) return false; 135 | Recipe recipe = (Recipe) o; 136 | return prepTime == recipe.prepTime && 137 | cookTime == recipe.cookTime && 138 | servings == recipe.servings && 139 | isVegetarian == recipe.isVegetarian && 140 | isVegan == recipe.isVegan && 141 | isPublished == recipe.isPublished && 142 | Objects.equals(recipeId, recipe.recipeId) && 143 | Objects.equals(name, recipe.name) && 144 | Objects.equals(description, recipe.description) && 145 | Objects.equals(credit, recipe.credit) && 146 | Objects.equals(ingredients, recipe.ingredients) && 147 | Objects.equals(instructions, recipe.instructions); 148 | } 149 | 150 | @Override 151 | public int hashCode() 152 | { 153 | return Objects.hash(recipeId, name, description, prepTime, cookTime, servings, isVegetarian, isVegan, isPublished, credit, ingredients, instructions); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /docs/ViewModels.md: -------------------------------------------------------------------------------- 1 | # Constructing View Models using SQL 2 | 3 | User interfaces often need to display data from many different tables. Or, they need to show summaries and aggregations of data, such as averages, counts, or sums. One method for getting this data is to select all the entities that contain the necessary data and construct the view model in code. But there are several problems with this approach. First, you end up selecting more data than you need from the database, which creates unnecessary latency in your query and load on your database and application servers. Second, your entity object graphs become too large because they have to support these queries. A better approach is to have separate models that are specifically for displaying and reporting. 4 | 5 | Photon makes it easy to construct custom view models using plain SQL. 6 | 7 | ```java 8 | try(PhotonTransaction transaction = photon.beginTransaction()) 9 | { 10 | String sql = 11 | "SELECT Product.id, Product.name, Order.orderDate " + 12 | "FROM Product " + 13 | "JOIN ProductOrders ON ProductOrders.productId = Product.id " + 14 | "JOIN Orders ON Orders.id = ProductOrder.orderId " + 15 | "WHERE Orders.orderDate > :orderDate " + 16 | "ORDER Orders.orderDate DESC "; 17 | 18 | List productOrdersAfterDate = transaction 19 | .query(sql) 20 | .addParameter("orderDate", new Date("2017/06/15")) 21 | .fetchList(ProductOrderDto.class); 22 | 23 | return productOrdersAfterDate; 24 | } 25 | ``` 26 | 27 | Query results are automatically mapped to the view model fields by name. Unlike aggregates, view models do not need to be pre-registered with Photon. 28 | 29 | It's also possible to fetch scalar values and lists using plain SQL: 30 | 31 | ```java 32 | try(PhotonTransaction transaction = photon.beginTransaction()) 33 | { 34 | String sql = 35 | "SELECT DISTINCT name " + 36 | "FROM Product " + 37 | "WHERE CHAR_LENGTH(name) > :nameLength "; 38 | 39 | List longProductNames = transaction 40 | .query(sql) 41 | .addParameter("nameLength", 8) 42 | .fetchScalarList(String.class); 43 | 44 | return longProductNames; 45 | } 46 | ``` 47 | 48 | If you want to construct a view model that consists of root objects each containing a list of child objects, you could write multiple `SELECT` statements and aggregate the data yourself into a single DTO, but this quickly becomes tedious and error prone. Photon offers support for constructing aggregate view models so that you only have to write one `SELECT` statement. Aggregate view models use the same builder as regular aggregates. 49 | 50 | ```java 51 | public class ProductOrdersDto 52 | { 53 | public long productId; 54 | 55 | public String productName; 56 | 57 | public List productOrders; 58 | } 59 | 60 | public class ProductOrderDto 61 | { 62 | public long orderId; 63 | 64 | public Date orderDate; 65 | } 66 | ``` 67 | 68 | ```java 69 | // Register the view model aggregate. You can re-use the same DTO classes in multiple view model aggregates (e.g. if you want different 70 | // view models with different sort orders), just give each aggregate view model a unique name. 71 | 72 | photon 73 | .registerViewModelAggregate(ProductOrdersDto.class, "ProductOrdersMostRecentFirst") 74 | .withId("productId") 75 | .withChild("productOrders", ProductOrderDto.class) 76 | .withForeignKeyToParent("productOrderId") 77 | .withOrderBySql("Order.orderDate DESC") 78 | .addAsChild() 79 | .register(); 80 | 81 | // Create a query similar to querying for a regular aggregate. 82 | 83 | try(PhotonTransaction transaction = photon.beginTransaction()) 84 | { 85 | String sql = 86 | "SELECT Product.id " + 87 | "FROM Product " + 88 | "JOIN ProductOrders ON ProductOrders.productId = Product.id " + 89 | "JOIN Orders ON Orders.id = ProductOrder.orderId " + 90 | "GROUP BY Product.id " + 91 | "HAVING COUNT(DISTINCT Orders.id) >= :minOrderCount "; 92 | 93 | List productOrders = transaction 94 | .query(ProductOrdersDto.class, "ProductOrdersMostRecentFirst") 95 | .whereIdIn(sql) 96 | .addParameter("minOrderCount", 3) 97 | .fetchList(); 98 | 99 | return productOrders; 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/integration/MySqlIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.integration; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.options.PhotonOptions; 6 | import org.junit.After; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.time.Instant; 11 | import java.time.ZoneId; 12 | import java.time.ZonedDateTime; 13 | import java.util.TimeZone; 14 | import java.util.UUID; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | import static org.junit.Assert.assertNotNull; 18 | 19 | public class MySqlIntegrationTest 20 | { 21 | private Photon photon; 22 | private TimeZone timeZone; 23 | 24 | @Before 25 | public void setup() 26 | { 27 | timeZone = TimeZone.getDefault(); 28 | TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago")); 29 | 30 | String url = "jdbc:mysql://localhost:13306"; 31 | photon = new Photon(url, "root", "bears", PhotonOptions.mysqlOptions().build()); 32 | 33 | try(PhotonTransaction transaction = photon.beginTransaction()) 34 | { 35 | transaction.query( 36 | "CREATE DATABASE IF NOT EXISTS photon_test_db" 37 | ).executeInsert(); 38 | transaction.commit(); 39 | } 40 | 41 | url = "jdbc:mysql://localhost:13306/photon_test_db"; 42 | photon = new Photon(url, "root", "bears", PhotonOptions.mysqlOptions().build()); 43 | 44 | photon 45 | .registerAggregate(PhotonTestTable.class) 46 | .withId("id") 47 | .withPrimaryKeyAutoIncrement() 48 | .register(); 49 | 50 | try(PhotonTransaction transaction = photon.beginTransaction()) 51 | { 52 | transaction.query( 53 | "DROP TABLE IF EXISTS PhotonTestTable" 54 | ).executeInsert(); 55 | 56 | transaction.query( 57 | "CREATE TABLE PhotonTestTable( " + 58 | "`id` int NOT NULL AUTO_INCREMENT, " + 59 | "`uuidColumn` binary(16) NOT NULL, " + 60 | "`dateColumn` datetime NOT NULL, " + 61 | "`varcharColumn` varchar(50) NOT NULL, " + 62 | "PRIMARY KEY (`id`) " + 63 | ") " 64 | ).executeInsert(); 65 | 66 | transaction.query("INSERT INTO PhotonTestTable VALUES (DEFAULT, UNHEX('8ED1E1BD253E4469B4CB71E1217825B7'), FROM_UNIXTIME(1489915698), 'Test String')").executeInsert(); 67 | 68 | transaction.commit(); 69 | } 70 | } 71 | 72 | @After 73 | public void cleanUp() 74 | { 75 | TimeZone.setDefault(timeZone); 76 | } 77 | 78 | @Test 79 | public void fetchExistingAggregateById_populatesValues() 80 | { 81 | try(PhotonTransaction transaction = photon.beginTransaction()) 82 | { 83 | PhotonTestTable photonTestTable = transaction.query(PhotonTestTable.class).fetchById(1); 84 | 85 | assertNotNull(photonTestTable); 86 | assertEquals(1, photonTestTable.getId()); 87 | assertEquals(UUID.fromString("8ED1E1BD-253E-4469-B4CB-71E1217825B7"), photonTestTable.getUuidColumn()); 88 | assertEquals(ZonedDateTime.ofInstant(Instant.ofEpochMilli(1489915698000L), ZoneId.systemDefault()), photonTestTable.getDateColumn()); 89 | assertEquals("Test String", photonTestTable.getVarcharColumn()); 90 | } 91 | } 92 | 93 | @Test 94 | public void insertAggregateAndFetch_insertsAggregateAndPopulatesValues() 95 | { 96 | PhotonTestTable photonTestTable = new PhotonTestTable( 97 | null, 98 | UUID.fromString("11111111-2222-3333-4444-555555555555"), 99 | ZonedDateTime.ofInstant(Instant.ofEpochSecond(1493493022), ZoneId.systemDefault()), 100 | "My Test String" 101 | ); 102 | 103 | try(PhotonTransaction transaction = photon.beginTransaction()) 104 | { 105 | transaction.insert(photonTestTable); 106 | transaction.commit(); 107 | } 108 | 109 | try(PhotonTransaction transaction = photon.beginTransaction()) 110 | { 111 | PhotonTestTable photonTestTableFetched = transaction.query(PhotonTestTable.class).fetchById(2); 112 | 113 | assertEquals(photonTestTable, photonTestTableFetched); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /photon-core/src/main/java/com/github/molcikas/photon/blueprints/AggregateBlueprint.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.blueprints; 2 | 3 | import com.github.molcikas.photon.blueprints.entity.EntityBlueprint; 4 | import com.github.molcikas.photon.blueprints.entity.FieldBlueprint; 5 | import com.github.molcikas.photon.exceptions.PhotonException; 6 | import com.github.molcikas.photon.options.PhotonOptions; 7 | import com.github.molcikas.photon.sqlbuilders.DeleteSqlBuilderService; 8 | import com.github.molcikas.photon.sqlbuilders.InsertSqlBuilderService; 9 | import com.github.molcikas.photon.sqlbuilders.SelectSqlBuilderService; 10 | import com.github.molcikas.photon.sqlbuilders.UpdateSqlBuilderService; 11 | import lombok.AllArgsConstructor; 12 | import lombok.EqualsAndHashCode; 13 | import lombok.Getter; 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import java.util.*; 17 | import java.util.stream.Collectors; 18 | 19 | public class AggregateBlueprint 20 | { 21 | @AllArgsConstructor 22 | @Getter 23 | @EqualsAndHashCode 24 | private class EntityBlueprintKey 25 | { 26 | private final String fieldPath; 27 | private final int order; 28 | } 29 | 30 | private final EntityBlueprint aggregateRootEntityBlueprint; 31 | private final Map entityBlueprints; 32 | 33 | public EntityBlueprint getAggregateRootEntityBlueprint() 34 | { 35 | return aggregateRootEntityBlueprint; 36 | } 37 | 38 | public Class getAggregateRootClass() 39 | { 40 | return aggregateRootEntityBlueprint.getEntityClass(); 41 | } 42 | 43 | public AggregateBlueprint( 44 | EntityBlueprint aggregateRootEntityBlueprint, 45 | PhotonOptions photonOptions) 46 | { 47 | if(aggregateRootEntityBlueprint == null) 48 | { 49 | throw new PhotonException("Aggregate root entity cannot be null."); 50 | } 51 | 52 | this.entityBlueprints = new HashMap<>(); 53 | findAllEntityBlueprintsRecursive(aggregateRootEntityBlueprint, "", new Integer[] {0}); 54 | 55 | this.aggregateRootEntityBlueprint = aggregateRootEntityBlueprint; 56 | SelectSqlBuilderService.buildSelectSqlTemplates(aggregateRootEntityBlueprint, photonOptions); 57 | UpdateSqlBuilderService.buildUpdateSqlTemplates(aggregateRootEntityBlueprint, photonOptions); 58 | InsertSqlBuilderService.buildInsertSqlTemplates(aggregateRootEntityBlueprint, photonOptions); 59 | DeleteSqlBuilderService.buildDeleteSqlTemplates(aggregateRootEntityBlueprint, photonOptions); 60 | } 61 | 62 | public List getEntityBlueprints(List excludedFieldPaths) 63 | { 64 | List fieldPaths = entityBlueprints 65 | .keySet() 66 | .stream() 67 | .map(EntityBlueprintKey::getFieldPath) 68 | .collect(Collectors.toList()); 69 | 70 | Optional missingPath = excludedFieldPaths 71 | .stream() 72 | .filter(f -> !fieldPaths.contains(f)) 73 | .findFirst(); 74 | if(missingPath.isPresent()) 75 | { 76 | throw new PhotonException( 77 | "The field path '%s' does not exist for '%s'.", 78 | missingPath.get(), 79 | aggregateRootEntityBlueprint.getEntityClassName() 80 | ); 81 | } 82 | 83 | return entityBlueprints 84 | .entrySet() 85 | .stream() 86 | .filter(e -> !excludedFieldPaths.contains(e.getKey().getFieldPath())) 87 | .sorted(Comparator.comparingInt(e -> e.getKey().getOrder())) 88 | .map(Map.Entry::getValue) 89 | .collect(Collectors.toList()); 90 | } 91 | 92 | /** 93 | * Pass the order inside an array so that it gets passed by reference instead of by value. 94 | * 95 | * @param entityBlueprint 96 | * @param fieldPath 97 | * @param order 98 | */ 99 | private void findAllEntityBlueprintsRecursive(EntityBlueprint entityBlueprint, String fieldPath, Integer[] order) 100 | { 101 | entityBlueprints.put(new EntityBlueprintKey(fieldPath, order[0]), entityBlueprint); 102 | order[0]++; 103 | for(FieldBlueprint fieldBlueprint : entityBlueprint.getFieldsWithChildEntities()) 104 | { 105 | String childFieldPath = fieldPath + (StringUtils.isEmpty(fieldPath) ? "" : ".") + fieldBlueprint.getFieldName(); 106 | findAllEntityBlueprintsRecursive(fieldBlueprint.getChildEntityBlueprint(), childFieldPath, order); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/unit/h2/myonetomanytable/MyOneToManyTableDbSetup.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.unit.h2.myonetomanytable; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.tests.unit.h2.H2TestUtil; 6 | 7 | public class MyOneToManyTableDbSetup 8 | { 9 | public static Photon setupDatabase() 10 | { 11 | Photon photon = new Photon(H2TestUtil.h2Url, H2TestUtil.h2User, H2TestUtil.h2Password); 12 | 13 | try(PhotonTransaction transaction = photon.beginTransaction()) 14 | { 15 | transaction.query("DROP TABLE IF EXISTS `myonetomanytable`").executeUpdate(); 16 | transaction.query("CREATE TABLE `myonetomanytable` (\n" + 17 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 18 | " `myvalue` varchar(255) DEFAULT 'oops',\n" + 19 | " PRIMARY KEY (`id`)\n" + 20 | ")").executeUpdate(); 21 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (1, 'my1dbvalue')").executeUpdate(); 22 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (2, 'my2dbvalue')").executeUpdate(); 23 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (3, 'my3dbvalue')").executeUpdate(); 24 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (4, 'my4dbvalue')").executeUpdate(); 25 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (5, 'my5dbvalue')").executeUpdate(); 26 | transaction.query("insert into `myonetomanytable` (`id`, `myvalue`) values (6, 'my6dbvalue')").executeUpdate(); 27 | 28 | transaction.query("DROP TABLE IF EXISTS `mymanytable`").executeUpdate(); 29 | transaction.query("CREATE TABLE `mymanytable` (\n" + 30 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 31 | " `parent` int(11) NOT NULL,\n" + 32 | " `myothervalue` varchar(255) DEFAULT 'oops',\n" + 33 | " PRIMARY KEY (`id`),\n" + 34 | " CONSTRAINT `MyOneToManyTable_MyManyTable` FOREIGN KEY (`parent`) REFERENCES `myonetomanytable` (`id`)\n" + 35 | ")").executeUpdate(); 36 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (1, 3, 'my31otherdbvalue')").executeUpdate(); 37 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (2, 4, 'my41otherdbvalue')").executeUpdate(); 38 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (3, 4, 'my42otherdbvalue')").executeUpdate(); 39 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (4, 5, 'my51otherdbvalue')").executeUpdate(); 40 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (5, 5, 'my52otherdbvalue')").executeUpdate(); 41 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (6, 5, 'my53otherdbvalue')").executeUpdate(); 42 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (7, 6, 'my61otherdbvalue')").executeUpdate(); 43 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (8, 6, 'my62otherdbvalue')").executeUpdate(); 44 | transaction.query("insert into `mymanytable` (`id`, `parent`, `myothervalue`) values (9, 6, 'my63otherdbvalue')").executeUpdate(); 45 | 46 | transaction.query("DROP TABLE IF EXISTS `mythirdtable`").executeUpdate(); 47 | transaction.query("CREATE TABLE `mythirdtable` (\n" + 48 | " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + 49 | " `parent` int(11) NOT NULL,\n" + 50 | " `val` varchar(255) DEFAULT 'oops',\n" + 51 | " PRIMARY KEY (`id`),\n" + 52 | " CONSTRAINT `MyManyTable_MyThirdTable` FOREIGN KEY (`parent`) REFERENCES `mymanytable` (`id`)\n" + 53 | ")").executeUpdate(); 54 | transaction.query("insert into `mythirdtable` (`id`, `parent`, `val`) values (1, 7, 'thirdtableval1')").executeUpdate(); 55 | transaction.query("insert into `mythirdtable` (`id`, `parent`, `val`) values (2, 8, 'thirdtableval2')").executeUpdate(); 56 | transaction.query("insert into `mythirdtable` (`id`, `parent`, `val`) values (3, 9, 'thirdtableval3')").executeUpdate(); 57 | transaction.query("insert into `mythirdtable` (`id`, `parent`, `val`) values (4, 9, 'thirdtableval4')").executeUpdate(); 58 | 59 | transaction.commit(); 60 | } 61 | 62 | return photon; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /photon-core/src/test/java/com/github/molcikas/photon/tests/integration/OracleIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.github.molcikas.photon.tests.integration; 2 | 3 | import com.github.molcikas.photon.Photon; 4 | import com.github.molcikas.photon.PhotonTransaction; 5 | import com.github.molcikas.photon.options.PhotonOptions; 6 | import org.junit.Before; 7 | import org.junit.Ignore; 8 | import org.junit.Test; 9 | 10 | import java.time.Instant; 11 | import java.time.ZoneId; 12 | import java.time.ZonedDateTime; 13 | import java.util.Date; 14 | import java.util.TimeZone; 15 | import java.util.UUID; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertNotNull; 19 | 20 | /** 21 | * For running these tests, you will need to download and install the Oracle JDBC driver (ojdbc6.jar) to src/test/libs. 22 | */ 23 | @Ignore("The oracle docker container takes a very long time to start, so only run this test manually.") 24 | public class OracleIntegrationTest 25 | { 26 | private Photon photon; 27 | 28 | @Before 29 | public void setup() 30 | { 31 | String url = "jdbc:oracle:thin:@localhost:11521:xe"; 32 | photon = new Photon(url, "system", "oracle", PhotonOptions.oracleOptions().build()); 33 | 34 | photon 35 | .registerAggregate(PhotonTestTable.class) 36 | .withId("id") 37 | .withPrimaryKeyAutoIncrement() 38 | .register(); 39 | 40 | try(PhotonTransaction transaction = photon.beginTransaction()) 41 | { 42 | transaction.query( 43 | "BEGIN\n" + 44 | " EXECUTE IMMEDIATE 'DROP TABLE PHOTONTESTTABLE';\n" + 45 | "EXCEPTION\n" + 46 | " WHEN OTHERS THEN\n" + 47 | " IF SQLCODE != -942 THEN\n" + 48 | " RAISE;\n" + 49 | " END IF;\n" + 50 | "END;" 51 | ).executeInsert(); 52 | 53 | transaction.query( 54 | "CREATE TABLE PHOTONTESTTABLE( " + 55 | "ID NUMBER GENERATED AS IDENTITY, " + 56 | "UUIDCOLUMN RAW(16) NOT NULL, " + 57 | "DATECOLUMN DATE NOT NULL, " + 58 | "VARCHARCOLUMN VARCHAR2(50) NOT NULL, " + 59 | "CONSTRAINT PHOTONTESTTABLE_PK PRIMARY KEY (ID) " + 60 | ")" 61 | ).executeInsert(); 62 | 63 | transaction.query("INSERT INTO PhotonTestTable VALUES (DEFAULT, '8ED1E1BD253E4469B4CB71E1217825B7', DATE '1970-01-01' + 1489915698/24/60/60, 'Test String')").executeInsert(); 64 | 65 | transaction.commit(); 66 | } 67 | } 68 | 69 | // TODO: This test fails if run during standard time instead of DST. The time is off by one hour. 70 | @Test 71 | public void fetchExistingAggregateById_populatesValues() 72 | { 73 | try(PhotonTransaction transaction = photon.beginTransaction()) 74 | { 75 | PhotonTestTable photonTestTable = transaction.query(PhotonTestTable.class).fetchById(1); 76 | 77 | // The database does not store a time zone, so we assume the date is in the system's time zone. But to make these tests 78 | // compare epoch times but still work with any system time zone, we have to offset the epoch to the system's time zone. 79 | int currentUtcOffset = TimeZone.getDefault().getOffset(new Date().getTime()); 80 | 81 | assertNotNull(photonTestTable); 82 | assertEquals(1, photonTestTable.getId()); 83 | assertEquals(UUID.fromString("8ED1E1BD-253E-4469-B4CB-71E1217825B7"), photonTestTable.getUuidColumn()); 84 | assertEquals(ZonedDateTime.ofInstant(Instant.ofEpochMilli(1489915698000L - currentUtcOffset), ZoneId.systemDefault()), photonTestTable.getDateColumn()); 85 | assertEquals("Test String", photonTestTable.getVarcharColumn()); 86 | } 87 | } 88 | 89 | @Test 90 | public void insertAggregateAndFetch_insertsAggregateAndPopulatesValues() 91 | { 92 | PhotonTestTable photonTestTable = new PhotonTestTable( 93 | null, 94 | UUID.fromString("11111111-2222-3333-4444-555555555555"), 95 | ZonedDateTime.ofInstant(Instant.ofEpochSecond(1493493022), ZoneId.systemDefault()), 96 | "My Test String" 97 | ); 98 | 99 | try(PhotonTransaction transaction = photon.beginTransaction()) 100 | { 101 | transaction.insert(photonTestTable); 102 | transaction.commit(); 103 | } 104 | 105 | try(PhotonTransaction transaction = photon.beginTransaction()) 106 | { 107 | PhotonTestTable photonTestTableFetched = transaction.query(PhotonTestTable.class).fetchById(2); 108 | 109 | assertEquals(photonTestTable, photonTestTableFetched); 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------