Code for video tutorial
diff --git a/.gitignore b/.gitignore
index e69de29..c3484a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.idea
+*.iml
+target
+.DS_Store
diff --git a/cayenne-video-tutorial-01/cayenne-demo.sql b/cayenne-video-tutorial-01/cayenne-demo.sql
new file mode 100644
index 0000000..e433fa5
--- /dev/null
+++ b/cayenne-video-tutorial-01/cayenne-demo.sql
@@ -0,0 +1,12 @@
+create table cayenne.gallery (gallery_id int not null auto_increment, gallery_name varchar(100) not null, primary key (gallery_id)) engine=innodb;
+create table cayenne.artist (artist_id bigint not null auto_increment, artist_name char(254) not null, date_of_birth date null, primary key (artist_id)) engine=innodb;
+create table cayenne.exhibit (closing_date datetime not null, exhibit_id int not null auto_increment, gallery_id int not null, opening_date datetime not null, primary key (exhibit_id)) engine=innodb;
+create table cayenne.painting (artist_id bigint null, estimated_price decimal(16, 2) null, painting_description varchar(255) null, painting_id int not null auto_increment, painting_title varchar(255) not null, primary key (painting_id)) engine=innodb;
+create table cayenne.painting_info (image_blob longblob null, painting_id int not null, review longtext null, primary key (painting_id)) engine=innodb;
+create table cayenne.painting_exhibit (exhibit_id int not null, painting_id int not null, primary key (exhibit_id, painting_id)) engine=innodb;
+
+alter table cayenne.exhibit add foreign key (gallery_id) references cayenne.gallery (gallery_id);
+alter table cayenne.painting add foreign key (artist_id) references cayenne.artist (artist_id);
+alter table cayenne.painting_info add foreign key (painting_id) references cayenne.painting (painting_id);
+alter table cayenne.painting_exhibit add foreign key (exhibit_id) references cayenne.exhibit (exhibit_id);
+alter table cayenne.painting_exhibit add foreign key (painting_id) references cayenne.painting (painting_id);
diff --git a/cayenne-video-tutorial-01/pom.xml b/cayenne-video-tutorial-01/pom.xml
new file mode 100644
index 0000000..ff1d5c9
--- /dev/null
+++ b/cayenne-video-tutorial-01/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.cayenne.demo</groupId>
+    <artifactId>cayenne-demo</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>4.1.B1</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.13</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/Application.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/Application.java
new file mode 100644
index 0000000..33c2a03
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/Application.java
@@ -0,0 +1,86 @@
+package org.apache.cayenne.demo;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.datasource.DataSourceBuilder;
+import org.apache.cayenne.demo.model.Artist;
+import org.apache.cayenne.demo.model.Painting;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.SelectById;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+
+public class Application {
+
+    private final ServerRuntime cayenneRuntime;
+
+    public static void main(String[] args) {
+        Application app = new Application();
+//        app.insertArtist();
+//        app.updateArtist();
+//        app.insertPaintings();
+        app.selectPaintings();
+    }
+
+    private Application() {
+        cayenneRuntime = ServerRuntime.builder()
+                .addConfig("cayenne-project.xml")
+                .dataSource(DataSourceBuilder.url("jdbc:mysql://localhost/cayenne")
+                        .driver(com.mysql.cj.jdbc.Driver.class.getName())
+                        .userName("root")
+                        .password("cayenne")
+                        .pool(1, 3).build())
+                .build();
+    }
+
+    private void selectPaintings() {
+        ObjectContext context = cayenneRuntime.newContext();
+
+        List<Painting> paintingList = ObjectSelect.query(Painting.class)
+                .where(Painting.ESTIMATED_PRICE.gt(BigDecimal.valueOf(100_000_000)))
+                .and(Painting.ARTIST.dot(Artist.DATE_OF_BIRTH).gt(LocalDate.of(1800, 1, 1)))
+                .prefetch(Painting.ARTIST.joint())
+                .orderBy(Painting.TITLE.asc())
+                .select(context);
+
+        paintingList.forEach(System.out::println);
+    }
+
+    private void insertPaintings() {
+        ObjectContext context = cayenneRuntime.newContext();
+
+        Artist picasso = ObjectSelect.query(Artist.class, Artist.NAME.eq("Pablo Picasso")).selectOne(context);
+
+        Painting boy = context.newObject(Painting.class);
+        boy.setTitle("Boy with a Pipe");
+        boy.setEstimatedPrice(BigDecimal.valueOf(104_168_000));
+        boy.setArtist(picasso);
+
+        Painting drinker = context.newObject(Painting.class);
+        drinker.setArtist(picasso);
+        drinker.setTitle("Absinthe Drinker");
+
+        context.commitChanges();
+    }
+
+    private void updateArtist() {
+        ObjectContext context = cayenneRuntime.newContext();
+
+        Artist picasso = SelectById.query(Artist.class, 1).selectOne(context);
+        picasso.setDateOfBirth(LocalDate.of(1881, 10, 25));
+
+        context.commitChanges();
+    }
+
+    private void insertArtist() {
+        ObjectContext context = cayenneRuntime.newContext();
+
+        Artist picasso = context.newObject(Artist.class);
+        picasso.setName("Pablo Picasso");
+
+        context.commitChanges();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Artist.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Artist.java
new file mode 100644
index 0000000..5706605
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Artist.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.demo.model;
+
+import org.apache.cayenne.demo.model.auto._Artist;
+
+public class Artist extends _Artist {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Exhibit.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Exhibit.java
new file mode 100644
index 0000000..28dd8d2
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Exhibit.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.demo.model;
+
+import org.apache.cayenne.demo.model.auto._Exhibit;
+
+public class Exhibit extends _Exhibit {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Gallery.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Gallery.java
new file mode 100644
index 0000000..6ae45a4
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Gallery.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.demo.model;
+
+import org.apache.cayenne.demo.model.auto._Gallery;
+
+public class Gallery extends _Gallery {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Painting.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Painting.java
new file mode 100644
index 0000000..51a3d43
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/Painting.java
@@ -0,0 +1,17 @@
+package org.apache.cayenne.demo.model;
+
+import org.apache.cayenne.demo.model.auto._Painting;
+
+public class Painting extends _Painting {
+
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public String toString() {
+        return "Painting{" +
+                "title='" + title + '\'' +
+                ", objectId=" + objectId +
+                ", artist=" + getArtist().getName() +
+                '}';
+    }
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/PaintingInfo.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/PaintingInfo.java
new file mode 100644
index 0000000..aa49c4b
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/PaintingInfo.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.demo.model;
+
+import org.apache.cayenne.demo.model.auto._PaintingInfo;
+
+public class PaintingInfo extends _PaintingInfo {
+
+    private static final long serialVersionUID = 1L; 
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Artist.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Artist.java
new file mode 100644
index 0000000..7081428
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Artist.java
@@ -0,0 +1,130 @@
+package org.apache.cayenne.demo.model.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.time.LocalDate;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.demo.model.Painting;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _Artist was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Artist extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String ARTIST_ID_PK_COLUMN = "artist_id";
+
+    public static final Property<LocalDate> DATE_OF_BIRTH = Property.create("dateOfBirth", LocalDate.class);
+    public static final Property<String> NAME = Property.create("name", String.class);
+    public static final Property<List<Painting>> PAINTINGS = Property.create("paintings", List.class);
+
+    protected LocalDate dateOfBirth;
+    protected String name;
+
+    protected Object paintings;
+
+    public void setDateOfBirth(LocalDate dateOfBirth) {
+        beforePropertyWrite("dateOfBirth", this.dateOfBirth, dateOfBirth);
+        this.dateOfBirth = dateOfBirth;
+    }
+
+    public LocalDate getDateOfBirth() {
+        beforePropertyRead("dateOfBirth");
+        return this.dateOfBirth;
+    }
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    public void addToPaintings(Painting obj) {
+        addToManyTarget("paintings", obj, true);
+    }
+
+    public void removeFromPaintings(Painting obj) {
+        removeToManyTarget("paintings", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Painting> getPaintings() {
+        return (List<Painting>)readProperty("paintings");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "dateOfBirth":
+                return this.dateOfBirth;
+            case "name":
+                return this.name;
+            case "paintings":
+                return this.paintings;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "dateOfBirth":
+                this.dateOfBirth = (LocalDate)val;
+                break;
+            case "name":
+                this.name = (String)val;
+                break;
+            case "paintings":
+                this.paintings = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.dateOfBirth);
+        out.writeObject(this.name);
+        out.writeObject(this.paintings);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.dateOfBirth = (LocalDate)in.readObject();
+        this.name = (String)in.readObject();
+        this.paintings = in.readObject();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Exhibit.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Exhibit.java
new file mode 100644
index 0000000..c87e122
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Exhibit.java
@@ -0,0 +1,148 @@
+package org.apache.cayenne.demo.model.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.demo.model.Gallery;
+import org.apache.cayenne.demo.model.Painting;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _Exhibit was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Exhibit extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String EXHIBIT_ID_PK_COLUMN = "exhibit_id";
+
+    public static final Property<LocalDateTime> CLOSING_DATE = Property.create("closingDate", LocalDateTime.class);
+    public static final Property<LocalDateTime> OPENING_DATE = Property.create("openingDate", LocalDateTime.class);
+    public static final Property<Gallery> GALLERY = Property.create("gallery", Gallery.class);
+    public static final Property<List<Painting>> PAINTINGS = Property.create("paintings", List.class);
+
+    protected LocalDateTime closingDate;
+    protected LocalDateTime openingDate;
+
+    protected Object gallery;
+    protected Object paintings;
+
+    public void setClosingDate(LocalDateTime closingDate) {
+        beforePropertyWrite("closingDate", this.closingDate, closingDate);
+        this.closingDate = closingDate;
+    }
+
+    public LocalDateTime getClosingDate() {
+        beforePropertyRead("closingDate");
+        return this.closingDate;
+    }
+
+    public void setOpeningDate(LocalDateTime openingDate) {
+        beforePropertyWrite("openingDate", this.openingDate, openingDate);
+        this.openingDate = openingDate;
+    }
+
+    public LocalDateTime getOpeningDate() {
+        beforePropertyRead("openingDate");
+        return this.openingDate;
+    }
+
+    public void setGallery(Gallery gallery) {
+        setToOneTarget("gallery", gallery, true);
+    }
+
+    public Gallery getGallery() {
+        return (Gallery)readProperty("gallery");
+    }
+
+    public void addToPaintings(Painting obj) {
+        addToManyTarget("paintings", obj, true);
+    }
+
+    public void removeFromPaintings(Painting obj) {
+        removeToManyTarget("paintings", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Painting> getPaintings() {
+        return (List<Painting>)readProperty("paintings");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "closingDate":
+                return this.closingDate;
+            case "openingDate":
+                return this.openingDate;
+            case "gallery":
+                return this.gallery;
+            case "paintings":
+                return this.paintings;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "closingDate":
+                this.closingDate = (LocalDateTime)val;
+                break;
+            case "openingDate":
+                this.openingDate = (LocalDateTime)val;
+                break;
+            case "gallery":
+                this.gallery = val;
+                break;
+            case "paintings":
+                this.paintings = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.closingDate);
+        out.writeObject(this.openingDate);
+        out.writeObject(this.gallery);
+        out.writeObject(this.paintings);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.closingDate = (LocalDateTime)in.readObject();
+        this.openingDate = (LocalDateTime)in.readObject();
+        this.gallery = in.readObject();
+        this.paintings = in.readObject();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Gallery.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Gallery.java
new file mode 100644
index 0000000..39cf6c4
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Gallery.java
@@ -0,0 +1,110 @@
+package org.apache.cayenne.demo.model.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.demo.model.Exhibit;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _Gallery was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Gallery extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String GALLERY_ID_PK_COLUMN = "gallery_id";
+
+    public static final Property<String> NAME = Property.create("name", String.class);
+    public static final Property<List<Exhibit>> EXHIBITS = Property.create("exhibits", List.class);
+
+    protected String name;
+
+    protected Object exhibits;
+
+    public void setName(String name) {
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
+    }
+
+    public String getName() {
+        beforePropertyRead("name");
+        return this.name;
+    }
+
+    public void addToExhibits(Exhibit obj) {
+        addToManyTarget("exhibits", obj, true);
+    }
+
+    public void removeFromExhibits(Exhibit obj) {
+        removeToManyTarget("exhibits", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Exhibit> getExhibits() {
+        return (List<Exhibit>)readProperty("exhibits");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "exhibits":
+                return this.exhibits;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "exhibits":
+                this.exhibits = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.name);
+        out.writeObject(this.exhibits);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.name = (String)in.readObject();
+        this.exhibits = in.readObject();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Painting.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Painting.java
new file mode 100644
index 0000000..ca90620
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_Painting.java
@@ -0,0 +1,185 @@
+package org.apache.cayenne.demo.model.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigDecimal;
+import java.util.List;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.demo.model.Artist;
+import org.apache.cayenne.demo.model.Exhibit;
+import org.apache.cayenne.demo.model.PaintingInfo;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _Painting was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _Painting extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String PAINTING_ID_PK_COLUMN = "painting_id";
+
+    public static final Property<BigDecimal> ESTIMATED_PRICE = Property.create("estimatedPrice", BigDecimal.class);
+    public static final Property<String> DESCRIPTION = Property.create("description", String.class);
+    public static final Property<String> TITLE = Property.create("title", String.class);
+    public static final Property<PaintingInfo> PAINTING = Property.create("painting", PaintingInfo.class);
+    public static final Property<Artist> ARTIST = Property.create("artist", Artist.class);
+    public static final Property<List<Exhibit>> EXHIBITS = Property.create("exhibits", List.class);
+
+    protected BigDecimal estimatedPrice;
+    protected String description;
+    protected String title;
+
+    protected Object painting;
+    protected Object artist;
+    protected Object exhibits;
+
+    public void setEstimatedPrice(BigDecimal estimatedPrice) {
+        beforePropertyWrite("estimatedPrice", this.estimatedPrice, estimatedPrice);
+        this.estimatedPrice = estimatedPrice;
+    }
+
+    public BigDecimal getEstimatedPrice() {
+        beforePropertyRead("estimatedPrice");
+        return this.estimatedPrice;
+    }
+
+    public void setDescription(String description) {
+        beforePropertyWrite("description", this.description, description);
+        this.description = description;
+    }
+
+    public String getDescription() {
+        beforePropertyRead("description");
+        return this.description;
+    }
+
+    public void setTitle(String title) {
+        beforePropertyWrite("title", this.title, title);
+        this.title = title;
+    }
+
+    public String getTitle() {
+        beforePropertyRead("title");
+        return this.title;
+    }
+
+    public void setPainting(PaintingInfo painting) {
+        setToOneTarget("painting", painting, true);
+    }
+
+    public PaintingInfo getPainting() {
+        return (PaintingInfo)readProperty("painting");
+    }
+
+    public void setArtist(Artist artist) {
+        setToOneTarget("artist", artist, true);
+    }
+
+    public Artist getArtist() {
+        return (Artist)readProperty("artist");
+    }
+
+    public void addToExhibits(Exhibit obj) {
+        addToManyTarget("exhibits", obj, true);
+    }
+
+    public void removeFromExhibits(Exhibit obj) {
+        removeToManyTarget("exhibits", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<Exhibit> getExhibits() {
+        return (List<Exhibit>)readProperty("exhibits");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "estimatedPrice":
+                return this.estimatedPrice;
+            case "description":
+                return this.description;
+            case "title":
+                return this.title;
+            case "painting":
+                return this.painting;
+            case "artist":
+                return this.artist;
+            case "exhibits":
+                return this.exhibits;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "estimatedPrice":
+                this.estimatedPrice = (BigDecimal)val;
+                break;
+            case "description":
+                this.description = (String)val;
+                break;
+            case "title":
+                this.title = (String)val;
+                break;
+            case "painting":
+                this.painting = val;
+                break;
+            case "artist":
+                this.artist = val;
+                break;
+            case "exhibits":
+                this.exhibits = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.estimatedPrice);
+        out.writeObject(this.description);
+        out.writeObject(this.title);
+        out.writeObject(this.painting);
+        out.writeObject(this.artist);
+        out.writeObject(this.exhibits);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.estimatedPrice = (BigDecimal)in.readObject();
+        this.description = (String)in.readObject();
+        this.title = (String)in.readObject();
+        this.painting = in.readObject();
+        this.artist = in.readObject();
+        this.exhibits = in.readObject();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_PaintingInfo.java b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_PaintingInfo.java
new file mode 100644
index 0000000..b236489
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/java/org/apache/cayenne/demo/model/auto/_PaintingInfo.java
@@ -0,0 +1,123 @@
+package org.apache.cayenne.demo.model.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.demo.model.Painting;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _PaintingInfo was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _PaintingInfo extends BaseDataObject {
+
+    private static final long serialVersionUID = 1L; 
+
+    public static final String PAINTING_ID_PK_COLUMN = "painting_id";
+
+    public static final Property<String> REVIEW = Property.create("review", String.class);
+    public static final Property<byte[]> IMAGE = Property.create("image", byte[].class);
+    public static final Property<Painting> PAINTING = Property.create("painting", Painting.class);
+
+    protected String review;
+    protected byte[] image;
+
+    protected Object painting;
+
+    public void setReview(String review) {
+        beforePropertyWrite("review", this.review, review);
+        this.review = review;
+    }
+
+    public String getReview() {
+        beforePropertyRead("review");
+        return this.review;
+    }
+
+    public void setImage(byte[] image) {
+        beforePropertyWrite("image", this.image, image);
+        this.image = image;
+    }
+
+    public byte[] getImage() {
+        beforePropertyRead("image");
+        return this.image;
+    }
+
+    public void setPainting(Painting painting) {
+        setToOneTarget("painting", painting, true);
+    }
+
+    public Painting getPainting() {
+        return (Painting)readProperty("painting");
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "review":
+                return this.review;
+            case "image":
+                return this.image;
+            case "painting":
+                return this.painting;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "review":
+                this.review = (String)val;
+                break;
+            case "image":
+                this.image = (byte[])val;
+                break;
+            case "painting":
+                this.painting = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(this.review);
+        out.writeObject(this.image);
+        out.writeObject(this.painting);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        this.review = (String)in.readObject();
+        this.image = (byte[])in.readObject();
+        this.painting = in.readObject();
+    }
+
+}
diff --git a/cayenne-video-tutorial-01/src/main/resources/cayenne-project.xml b/cayenne-video-tutorial-01/src/main/resources/cayenne-project.xml
new file mode 100644
index 0000000..9f629d3
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/resources/cayenne-project.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain xmlns="http://cayenne.apache.org/schema/10/domain"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain https://cayenne.apache.org/schema/10/domain.xsd"
+	 project-version="10">
+	<map name="datamap"/>
+</domain>
diff --git a/cayenne-video-tutorial-01/src/main/resources/datamap.map.xml b/cayenne-video-tutorial-01/src/main/resources/datamap.map.xml
new file mode 100644
index 0000000..fb4354e
--- /dev/null
+++ b/cayenne-video-tutorial-01/src/main/resources/datamap.map.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
+	 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	 xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap https://cayenne.apache.org/schema/10/modelMap.xsd"
+	 project-version="10">
+	<property name="defaultPackage" value="org.apache.cayenne.demo.model"/>
+	<db-entity name="artist" catalog="cayenne">
+		<db-attribute name="artist_id" type="BIGINT" isPrimaryKey="true" isGenerated="true" isMandatory="true" length="19"/>
+		<db-attribute name="artist_name" type="CHAR" isMandatory="true" length="254"/>
+		<db-attribute name="date_of_birth" type="DATE" length="10"/>
+	</db-entity>
+	<db-entity name="exhibit" catalog="cayenne">
+		<db-attribute name="closing_date" type="TIMESTAMP" isMandatory="true" length="26"/>
+		<db-attribute name="exhibit_id" type="INTEGER" isPrimaryKey="true" isGenerated="true" isMandatory="true" length="10"/>
+		<db-attribute name="gallery_id" type="INTEGER" isMandatory="true" length="10"/>
+		<db-attribute name="opening_date" type="TIMESTAMP" isMandatory="true" length="26"/>
+	</db-entity>
+	<db-entity name="gallery" catalog="cayenne">
+		<db-attribute name="gallery_id" type="INTEGER" isPrimaryKey="true" isGenerated="true" isMandatory="true" length="10"/>
+		<db-attribute name="gallery_name" type="VARCHAR" isMandatory="true" length="100"/>
+	</db-entity>
+	<db-entity name="painting" catalog="cayenne">
+		<db-attribute name="artist_id" type="BIGINT" length="19"/>
+		<db-attribute name="estimated_price" type="DECIMAL" length="16" scale="2"/>
+		<db-attribute name="painting_description" type="VARCHAR" length="255"/>
+		<db-attribute name="painting_id" type="INTEGER" isPrimaryKey="true" isGenerated="true" isMandatory="true" length="10"/>
+		<db-attribute name="painting_title" type="VARCHAR" isMandatory="true" length="255"/>
+	</db-entity>
+	<db-entity name="painting_exhibit" catalog="cayenne">
+		<db-attribute name="exhibit_id" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="painting_id" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+	</db-entity>
+	<db-entity name="painting_info" catalog="cayenne">
+		<db-attribute name="image_blob" type="LONGVARBINARY" length="2147483647"/>
+		<db-attribute name="painting_id" type="INTEGER" isPrimaryKey="true" isMandatory="true" length="10"/>
+		<db-attribute name="review" type="LONGVARCHAR" length="2147483647"/>
+	</db-entity>
+	<obj-entity name="Artist" className="org.apache.cayenne.demo.model.Artist" dbEntityName="artist">
+		<obj-attribute name="dateOfBirth" type="java.time.LocalDate" db-attribute-path="date_of_birth"/>
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="artist_name"/>
+	</obj-entity>
+	<obj-entity name="Exhibit" className="org.apache.cayenne.demo.model.Exhibit" dbEntityName="exhibit">
+		<obj-attribute name="closingDate" type="java.time.LocalDateTime" db-attribute-path="closing_date"/>
+		<obj-attribute name="openingDate" type="java.time.LocalDateTime" db-attribute-path="opening_date"/>
+	</obj-entity>
+	<obj-entity name="Gallery" className="org.apache.cayenne.demo.model.Gallery" dbEntityName="gallery">
+		<obj-attribute name="name" type="java.lang.String" db-attribute-path="gallery_name"/>
+	</obj-entity>
+	<obj-entity name="Painting" className="org.apache.cayenne.demo.model.Painting" dbEntityName="painting">
+		<obj-attribute name="estimatedPrice" type="java.math.BigDecimal" db-attribute-path="estimated_price"/>
+		<obj-attribute name="description" type="java.lang.String" db-attribute-path="painting_description"/>
+		<obj-attribute name="title" type="java.lang.String" db-attribute-path="painting_title"/>
+	</obj-entity>
+	<obj-entity name="PaintingInfo" className="org.apache.cayenne.demo.model.PaintingInfo" dbEntityName="painting_info">
+		<obj-attribute name="review" type="java.lang.String" db-attribute-path="review"/>
+		<obj-attribute name="image" type="byte[]" db-attribute-path="image_blob"/>
+	</obj-entity>
+	<db-relationship name="paintings" source="artist" target="painting" toMany="true">
+		<db-attribute-pair source="artist_id" target="artist_id"/>
+	</db-relationship>
+	<db-relationship name="gallery" source="exhibit" target="gallery">
+		<db-attribute-pair source="gallery_id" target="gallery_id"/>
+	</db-relationship>
+	<db-relationship name="paintingExhibits" source="exhibit" target="painting_exhibit" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="exhibit_id" target="exhibit_id"/>
+	</db-relationship>
+	<db-relationship name="exhibits" source="gallery" target="exhibit" toMany="true">
+		<db-attribute-pair source="gallery_id" target="gallery_id"/>
+	</db-relationship>
+	<db-relationship name="paintingExhibits" source="painting" target="painting_exhibit" toDependentPK="true" toMany="true">
+		<db-attribute-pair source="painting_id" target="painting_id"/>
+	</db-relationship>
+	<db-relationship name="painting" source="painting" target="painting_info" toDependentPK="true">
+		<db-attribute-pair source="painting_id" target="painting_id"/>
+	</db-relationship>
+	<db-relationship name="artist" source="painting" target="artist">
+		<db-attribute-pair source="artist_id" target="artist_id"/>
+	</db-relationship>
+	<db-relationship name="painting" source="painting_exhibit" target="painting">
+		<db-attribute-pair source="painting_id" target="painting_id"/>
+	</db-relationship>
+	<db-relationship name="exhibit" source="painting_exhibit" target="exhibit">
+		<db-attribute-pair source="exhibit_id" target="exhibit_id"/>
+	</db-relationship>
+	<db-relationship name="painting" source="painting_info" target="painting">
+		<db-attribute-pair source="painting_id" target="painting_id"/>
+	</db-relationship>
+	<obj-relationship name="paintings" source="Artist" target="Painting" deleteRule="Deny" db-relationship-path="paintings"/>
+	<obj-relationship name="gallery" source="Exhibit" target="Gallery" deleteRule="Nullify" db-relationship-path="gallery"/>
+	<obj-relationship name="paintings" source="Exhibit" target="Painting" db-relationship-path="paintingExhibits.painting"/>
+	<obj-relationship name="exhibits" source="Gallery" target="Exhibit" deleteRule="Deny" db-relationship-path="exhibits"/>
+	<obj-relationship name="painting" source="Painting" target="PaintingInfo" deleteRule="Nullify" db-relationship-path="painting"/>
+	<obj-relationship name="artist" source="Painting" target="Artist" deleteRule="Nullify" db-relationship-path="artist"/>
+	<obj-relationship name="exhibits" source="Painting" target="Exhibit" db-relationship-path="paintingExhibits.exhibit"/>
+	<obj-relationship name="painting" source="PaintingInfo" target="Painting" deleteRule="Nullify" db-relationship-path="painting"/>
+	<dbImport xmlns="http://cayenne.apache.org/schema/10/dbimport">
+		<catalog>
+			<name>cayenne</name>
+		</catalog>
+		<forceDataMapCatalog>false</forceDataMapCatalog>
+		<forceDataMapSchema>false</forceDataMapSchema>
+		<namingStrategy>org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator</namingStrategy>
+		<skipPrimaryKeyLoading>false</skipPrimaryKeyLoading>
+		<skipRelationshipsLoading>false</skipRelationshipsLoading>
+		<useJava7Types>false</useJava7Types>
+		<usePrimitives>true</usePrimitives>
+	</dbImport>
+	<cgen xmlns="http://cayenne.apache.org/schema/10/cgen">
+		<destDir>../java</destDir>
+		<mode>entity</mode>
+		<template>templates/v4_1/subclass.vm</template>
+		<superTemplate>templates/v4_1/superclass.vm</superTemplate>
+		<outputPattern>*.java</outputPattern>
+		<makePairs>true</makePairs>
+		<usePkgPath>true</usePkgPath>
+		<overwrite>false</overwrite>
+		<createPropertyNames>false</createPropertyNames>
+		<createPKProperties>false</createPKProperties>
+		<client>false</client>
+	</cgen>
+</data-map>