This annotation also adds the {@link Transient @Transient} and
+ * {@link Value @Value("null")} annotations to the field.
+ */
+@Transient
+@Documented
+@Value("null") // NOSONAR
+@Retention(RUNTIME)
+@Target({FIELD, PARAMETER, ANNOTATION_TYPE})
+public @interface OneToOne {
+
+ /**
+ * Set to {@code true} if the annotated field is only a back reference in the
+ * child entity of the one-to-one relationship. This is {@code false} by
+ * default as the annotaton is mostly used o the parent side of the
+ * relationship.
+ *
+ *
Using the {@code @OneToOne} annotation in the child side instead of the
+ * parent is ussually just with the intention of having a back references to
+ * the parent. Therefore, when this property is set to {@code true} the
+ * {@link #readonly()} property is true {@code true} as well.
+ *
+ * @return whether he annotation is used as a back reference or not.
+ */
+ boolean backReference() default false;
+
+ /**
+ * Used to specify the name of the "foreing key" column of the child table.
+ * This is usually not necessary if the name of the column matches the name
+ * of the parent table followed by an {@code _id} suffix.
+ *
+ *
For example, given the parent table is {@code phone} and the child
+ * table is {@code phone_details}. By default, the annotation will look for
+ * the "foreign key" column {@code phone_id} in the {@code phone_details} table.
+ *
+ * @return the name of the "foreing key" column
+ */
+ String mappedBy() default "";
+
+ /**
+ * Should the entity on the annotated field be readonly. I.e., the entity is
+ * never persisted. Defaults to {@code false}.
+ *
+ * @return whether the annotated entoty is readonly or not
+ */
+ boolean readonly() default false;
+}
diff --git a/src/main/java/io/github/joselion/springr2dbcrelationships/annotations/ProjectionOf.java b/src/main/java/io/github/joselion/springr2dbcrelationships/annotations/ProjectionOf.java
new file mode 100644
index 0000000..1379d15
--- /dev/null
+++ b/src/main/java/io/github/joselion/springr2dbcrelationships/annotations/ProjectionOf.java
@@ -0,0 +1,33 @@
+package io.github.joselion.springr2dbcrelationships.annotations;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class or record to be a projection of another entity. This features
+ * usually works out-of-the-box on Spring Boot, but this annotation is required
+ * to work with the relational annotations provided by this package.
+ *
+ * @see
+ * Projections - Spring R2DBC Reference
+ *
+ */
+@Inherited
+@Documented
+@Retention(RUNTIME)
+@Target({TYPE, ANNOTATION_TYPE})
+public @interface ProjectionOf {
+
+ /**
+ * The class of the entity to be projected.
+ *
+ * @return the type of the projection
+ */
+ Class> value();
+}
diff --git a/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/ReflectException.java b/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/ReflectException.java
new file mode 100644
index 0000000..9c6b604
--- /dev/null
+++ b/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/ReflectException.java
@@ -0,0 +1,38 @@
+package io.github.joselion.springr2dbcrelationships.exceptions;
+
+import io.github.joselion.springr2dbcrelationships.helpers.Reflect;
+
+/**
+ * Runtime exception thrown when something goes wrong with a {@link Reflect}
+ * helper process.
+ */
+public final class ReflectException extends RuntimeException {
+
+ private ReflectException(final String message) {
+ super(message);
+ }
+
+ private ReflectException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a reflect exception with a detail message.
+ *
+ * @param message the detail message
+ * @return a reflect exception instance
+ */
+ public static ReflectException of(final String message) {
+ return new ReflectException(message);
+ }
+
+ /**
+ * Creates a reflect exception with the specified cause.
+ *
+ * @param cause the cause to this exception
+ * @return a reflect exception instance
+ */
+ public static ReflectException of(final Throwable cause) {
+ return new ReflectException(cause);
+ }
+}
diff --git a/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/RelationshipException.java b/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/RelationshipException.java
new file mode 100644
index 0000000..d0360af
--- /dev/null
+++ b/src/main/java/io/github/joselion/springr2dbcrelationships/exceptions/RelationshipException.java
@@ -0,0 +1,35 @@
+package io.github.joselion.springr2dbcrelationships.exceptions;
+
+/**
+ * Runtime exception thrown when something goes wrong on a relationship process.
+ */
+public final class RelationshipException extends RuntimeException {
+
+ private RelationshipException(final String message) {
+ super(message);
+ }
+
+ private RelationshipException(final Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * Creates a new relationship exception with a detail message.
+ *
+ * @param message the detail message
+ * @return a relationship exception instance
+ */
+ public static RelationshipException of(final String message) {
+ return new RelationshipException(message);
+ }
+
+ /**
+ * Creates a new relationship exception with the specified cause.
+ *
+ * @param cause the cause to this exception
+ * @return a relationship exception instance
+ */
+ public static RelationshipException of(final Throwable cause) {
+ return new RelationshipException(cause);
+ }
+}
diff --git a/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Commons.java b/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Commons.java
new file mode 100644
index 0000000..7db0b6c
--- /dev/null
+++ b/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Commons.java
@@ -0,0 +1,87 @@
+package io.github.joselion.springr2dbcrelationships.helpers;
+
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.joining;
+
+/**
+ * Common helpers.
+ */
+public final class Commons {
+
+ private Commons() {
+ throw new UnsupportedOperationException("Common is a helper class");
+ }
+
+ /**
+ * Convenience method to cast with a generic type.
+ *
+ * @param the type to cast the target to
+ * @param target the target to cast
+ * @return a value cast to the generic type
+ */
+ @SuppressWarnings("unchecked")
+ public static T cast(final Object target) {
+ return (T) target;
+ }
+
+ /**
+ * Capitalizes the first character of the provided string.
+ *
+ * @param value the string to capitalize
+ * @return the capitalized string
+ */
+ public static String capitalize(final String value) {
+ if (!value.isEmpty()) {
+ final var first = value.substring(0, 1);
+ final var rest = value.substring(1);
+
+ return first.toUpperCase().concat(rest);
+ }
+
+ return value;
+ }
+
+ /**
+ * Uncapitalizes the first character of the provided string.
+ *
+ * @param value the string to uncapitalize
+ * @return the uncapitalized string
+ */
+ public static String uncapitalize(final String value) {
+ if (!value.isEmpty()) {
+ final var first = value.substring(0, 1);
+ final var rest = value.substring(1);
+
+ return first.toLowerCase().concat(rest);
+ }
+
+ return value;
+ }
+
+ /**
+ * Transforms a string to snake-case format.
+ *
+ * @param value the string to transform
+ * @return the text in snake-case format
+ */
+ public static String toSnakeCase(final String value) {
+ return value
+ .replaceAll("([a-z])([A-Z]+)", "$1_$2")
+ .toLowerCase();
+ }
+
+ /**
+ * Transforms a string to camel-case format.
+ *
+ * @param value the string to transform
+ * @return the text in camel-case format
+ */
+ public static String toCamelCase(final String value) {
+ final var words = value.split("[\\W_]+");
+ final var joined = stream(words)
+ .map(Commons::capitalize)
+ .collect(joining());
+
+ return Commons.uncapitalize(joined);
+ }
+}
diff --git a/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Reflect.java b/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Reflect.java
new file mode 100644
index 0000000..b97e42c
--- /dev/null
+++ b/src/main/java/io/github/joselion/springr2dbcrelationships/helpers/Reflect.java
@@ -0,0 +1,272 @@
+package io.github.joselion.springr2dbcrelationships.helpers;
+
+import static java.lang.invoke.LambdaMetafactory.metafactory;
+import static java.lang.invoke.MethodType.methodType;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.eclipse.jdt.annotation.Nullable;
+
+import io.github.joselion.maybe.Maybe;
+import io.github.joselion.springr2dbcrelationships.exceptions.ReflectException;
+
+/**
+ * Reflection helper class.
+ */
+public final class Reflect {
+
+ private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+ private Reflect() {
+ throw new UnsupportedOperationException("Reflect is a helper class");
+ }
+
+ /**
+ * Finds and invokes the getter of a field in the provided target.
+ *
+ * @param the return type of the getter
+ * @param target the target instance were the getter is invoked
+ * @param field the field to invoke the getter
+ * @return the result of invoking the getter
+ * @throws ReflectException if the getter method cannot be found or fails to
+ * be invoked
+ */
+ @Nullable
+ public static T getter(final Object target, final Field field) {
+ final var targetType = field.getDeclaringClass();
+ final var fieldType = field.getType();
+ final var getterMethod = Reflect.findGetterMethod(field);
+ final var getterFn = Maybe
+ .from(() -> metafactory(
+ LOOKUP,
+ "apply",
+ methodType(Function.class),
+ methodType(Object.class, Object.class),
+ getterMethod,
+ methodType(fieldType, targetType)
+ ))
+ .map(CallSite::getTarget)
+ .solve(method -> method.invoke()) // NOSONAR
+ .solve(Commons::>cast)
+ .orThrow(ReflectException::of);
+
+ return getterFn.apply(target);
+ }
+
+ /**
+ * Curried version of {@link Reflect#getter(Object, Field)} overload.
+ *
+ * @param the return type of the getter
+ * @param field the field to invoke the getter
+ * @return a function that takes the target as parameter and returns the
+ * result of invoking the getter of the field
+ */
+ public static Function