The first question you need to answer is whether you intend to create a truly generic data access framework, or if you want it tailored to the particular project you're working on.
A generic framework has the benefit of being portable and fairly loosely coupled to the underlying DB + schema, but at the cost of pushing schema level information further up in the framework. The more generic you make the framework, the more of this knowledge you push up.
A tailored framework alleviates the higher levels of the application from worrying about the schema or DB details, but at the cost of cost of being less re-usable in other environments. As a corollary, the more specific you make the framework, the less easy it is to extend elsewhere.
If you're not sure on which of those to pick, the generic framework makes sense if you suspect there will be changes at the DB layer or the schema. Likewise, if those are pretty well locked down then the tailored framework is the better approach. Ultimately, you're optimizing the system to more easily handle where you think the change will occur.
As to whether or not you should refactor - the answer is No unless you're encountering a specific problem.
Worrying about the degree of coupling is nice, I suppose, but doesn't necessarily address a specific problem that you may be seeing. Refactoring based upon presumed issues within coupling is actually more likely to create a problem for you than just leaving it alone.
Likewise with expressing the state. If the current mechanism of an enum
is sufficient and doesn't present an apparent problem then don't change what you've got.
Overall, it sounds like you've designed a reasonably robust data access method for the domain you're working worth. Your question doesn't list any specific issues that are holding back your development, so I'd call it good and move to the next stage of development.
There are two concerns to deal with - which are orthogonal to each other:
1) The lower end of persistence
This is where you have to deal with Connections,Queries, Resultsets and the like. Typically you use the one or the other framework which gives you a facade-pattern at hand, which abstracts away the dirty stuff away so that you only tell, what you want to know(Resultset from Query) and where the framework could find it (Query and Connection). What you do with the resultset is up to you.
When your paradigm is OOP, there is an additional step involved: assembling the resultset to objects. Either you do it manually or have an object-relational-mapper doing the job for you.
2) The abstraction within your application
On top of this low level layer sits the abstraction with which your application deals. There is the DataAccessObject (DAO)-Layer or the Repository-Layer. Both generate Objects for your Application to work with.
Which one you are using has no effect on the answer of your question.
Your question is on the one hand on modeling (»How to handle relationships between objects« Answer: via Composition/Aggregation) and on how to fill the object (and its dependencies) with data.
Is it done using Dependency Injection and if so, where is the dependency created?
This is the way to go. There are two ways:
1) You make the DI explicit and define a constructor. When creating the Object, you are able to create all dependencies first and inject them via constructor or setter injection
2) You use a framework, which does the magic for you. Oftentimes reflection is involved, i.e. there are possibilities within some languages to examine and manipulate objects on the fly. The injection is done transparently for the user by the framework.
This is independend from the DAO/Repository-pattern.
I am not using an ORM tool and don't want to as I like to explore these basic patterns directly
That's a noble approach, but not in every case productive. Most of the time, you want to get stuff done. For educational purposes, you could go down that road. But I would not recommend it.
Look out for a flexible ORM-Framework, which gives you a helping hand when needed without forcing you in one way or the other. Not in every case is the ootB generated query effective. A good framework let's you use SQL when needed and takes only care of the assembling part.
Best Answer
Your architecture should look something like this:
You'll need one data source driver implementation for each type of data source. The CRUD interface will be the same regardless of data source.
Note that you can have more than just CRUD methods. You might want to return collections, for example. Just make sure that whatever you do on the Data Source side is translatable to your API on the CRUD side for all possible data sources.