The only solution I've found is to have a functional layer between the DB and the application. Data needs in the application require abstraction from the DB schema and vice versa, lest a change of schema become massive project surgery. This is not the same thing as using an ORM.
If the DB schema should be changed then the change must be accompanied by changes in the functional request layer that pulls/pushes data. Likewise, changes in the data representation within the application must be accompanied by changes in the data layer that pulls/pushes data.
What this approach adds in terms of coding requirements in data abstraction call code it saves in massive, rewrite-the-world project-shaking changes. In particular, it prevents those project-wide changes from turning into feature/mission creep and putting your team in an impossible situation.
An additional benefit to this approach is that you can implement such a layer now and in increments without getting too wild up front. For example, you can write a 1-for-1 table query layer that separates the calling code from the DB query, and replace the previous in-code queries (or ORM or DB framework calls) with calls to these new functions that accept and return exactly whatever you are dealing with right now. This is easy. Now if you need a DB change, do it with an accompanying change to the data layer code so it accepts and returns whatever it does now but deals with the DB change gracefully. You can then change the calling code itself if this makes sense.
Sometimes you discover something interesting about non-trivial data in this process -- namely that object-to-table translations are not 1-for-1 and that the shape of the data in code can never match its shape in a relational DB, but that a relational DB is the most reasonable way (sometimes the only way) to store the data if it might ever directly serve more than a single application.